#include <nrnpython.h>
#include <structmember.h>
#include <InterViews/resource.h>
#include <nrnoc2iv.h>
#include <ocjump.h>
#include "ivocvect.h"
#include "oclist.h"
#include "nrniv_mf.h"
#include "nrnpy_utils.h"
#include "../nrniv/shapeplt.h"
#include <vector>
#if defined(HAVE_DLFCN_H)
#include <dlfcn.h>
#endif

#if defined(NRNPYTHON_DYNAMICLOAD) && NRNPYTHON_DYNAMICLOAD > 0
// when compiled with different Python.h, force correct value
#undef NRNPYTHON_DYNAMICLOAD
#define NRNPYTHON_DYNAMICLOAD PY_MAJOR_VERSION
#endif

#if !defined(NRNCMAKE) && defined(__MINGW32__) && NRNPYTHON_DYNAMICLOAD > 0
// want to end up with a string like "hoc36"
#define HOCMOD_1(s) HOCMOD_2(s)
#define HOCMOD_2(s) #s
#define HOCMOD "hoc" HOCMOD_1(NRNPYTHON_DYNAMICLOAD)
#else
// TODO: didn't enable dynamic load and end up with error if below is not a string
#define HOCMOD "hoc"
#endif

extern PyTypeObject* psection_type;

// copied from nrnpy_nrn
typedef struct {
  PyObject_HEAD Section* sec_;
  char* name_;
  PyObject* cell_;
} NPySecObj;

extern "C" {

#include "parse.h"
extern void (*nrnpy_sectionlist_helper_)(void*, Object*);
extern Object** (*nrnpy_gui_helper_)(const char*, Object*);
extern Object** (*nrnpy_gui_helper3_)(const char*, Object*, int);
extern char** (*nrnpy_gui_helper3_str_)(const char*, Object*, int);
extern double (*nrnpy_object_to_double_)(Object*);
extern void* (*nrnpy_get_pyobj)(Object* obj);
extern void (*nrnpy_decref)(void* pyobj);
void lvappendsec_and_ref(void* sl, Section* sec);
extern Section* nrn_noerr_access();
extern void hoc_pushs(Symbol*);
extern double* hoc_evalpointer();
extern double cable_prop_eval(Symbol* sym);
extern void nrn_change_nseg(Section*, int);
extern Symlist* hoc_top_level_symlist;
extern Symlist* hoc_built_in_symlist;
extern Inst* hoc_pc;
extern void hoc_push_string();
extern char** hoc_strpop();
extern void hoc_objectvar();
extern Object* hoc_newobj1(Symbol*, int);
extern int ivoc_list_count(Object*);
extern Object** hoc_objpop();
extern Object* hoc_obj_look_inside_stack(int);
extern void hoc_object_component();
extern int nrn_inpython_;
extern int hoc_stack_type();
extern void hoc_call();
extern Objectdata* hoc_top_level_data;
extern void hoc_tobj_unref(Object**);
extern void hoc_unref_defer();
extern void sec_access_push();
extern PyObject* nrnpy_pushsec(PyObject*);
extern bool hoc_valid_stmt(const char*, Object*);
myPyMODINIT_FUNC nrnpy_nrn();
extern PyObject* nrnpy_cas(PyObject*, PyObject*);
extern PyObject* nrnpy_forall(PyObject*, PyObject*);
extern PyObject* nrnpy_newsecobj(PyObject*, PyObject*, PyObject*);
extern int section_object_seen;
extern Symbol* nrnpy_pyobj_sym_;
extern Symbol* nrn_child_sym;
extern int nrn_secref_nchild(Section*);
extern PyObject* nrnpy_hoc2pyobject(Object*);
PyObject* nrnpy_ho2po(Object*);
Object* nrnpy_po2ho(PyObject*);
extern Object* nrnpy_pyobject_in_obj(PyObject*);
static void pyobject_in_objptr(Object**, PyObject*);
extern IvocVect* (*nrnpy_vec_from_python_p_)(void*);
extern Object** (*nrnpy_vec_to_python_p_)(void*);
extern Object** (*nrnpy_vec_as_numpy_helper_)(int, double*);
extern Object* (*nrnpy_rvp_rxd_to_callable)(Object*);
int nrnpy_set_vec_as_numpy(PyObject* (*p)(int, double*));  // called by ctypes.
int nrnpy_set_gui_callback(PyObject*);
extern double** nrnpy_setpointer_helper(PyObject*, PyObject*);
extern Symbol* ivoc_alias_lookup(const char* name, Object* ob);
extern int nrn_netcon_weight(void*, double**);
extern int nrn_matrix_dim(void*, int);
extern NPySecObj* newpysechelp(Section* sec);

extern PyObject* pmech_types;  // Python map for name to Mechanism
extern PyObject* rangevars_;   // Python map for name to Symbol

extern "C" int hoc_max_builtin_class_id;
extern "C" int hoc_return_type_code;

static cTemplate* hoc_vec_template_;
static cTemplate* hoc_list_template_;
static cTemplate* hoc_sectionlist_template_;

// typestr returned by Vector.__array_interface__
// byteorder (first element) is modified at import time
// to reflect the system byteorder
// Allocate one extra character space in case we have a two character integer of
// bytes per double
// i.e. 16
static char array_interface_typestr[5] = "|f8";

// static pointer to neurons.doc.get_docstring function initialized at import
// time
static PyObject* pfunc_get_docstring = NULL;

// Methods unique to the HocTopLevelInterpreter type of HocObject
// follow the add_methods implementation of python3.6.2 in typeobject.c
// and the GenericGetAttr implementation in object.c
static PyObject* topmethdict = NULL;
static void add2topdict(PyObject*);

static const char* hocobj_docstring =
    "class neuron.hoc.HocObject - Hoc Object wrapper";

#if 1
}
#include <hoccontext.h>
extern "C" {
#else
extern Object* hoc_thisobject;
#define HocTopContextSet \
  if (hoc_thisobject) {  \
    abort();             \
  }                      \
  assert(hoc_thisobject == 0);
#define HocContextRestore /**/
#endif

/*
Because python types have so many methods, attempt to do all set and get
using a PyHocObject which has different amounts filled in as the information
is gathered with a view toward ultimately making a call to hoc_object_component.
That requires that array indices or function arguments are on the stack.
The major variant is that we may be at the top level. The function arg
case is easy since they all come as a tuple in the call method. The
array indices come sequentially with a series of calls to the
sequence method. A nice side effect of intermediate objects is the extra
efficiency of reuse that avoids symbol lookup. Sadly, the scalar case does
not give this since the value is set/get instead of  returning the
intermediate.
*/

namespace PyHoc {
enum ObjectType {
  HocTopLevelInterpreter = 0,
  HocObject = 1,
  HocFunction = 2,  // function or TEMPLATE
  HocArray = 3,
  HocRefNum = 4,
  HocRefStr = 5,
  HocRefObj = 6,
  HocForallSectionIterator = 7,
  HocSectionListIterator = 8,
  HocScalarPtr = 9,
  HocArrayIncomplete =
      10,  // incomplete pointer to a hoc array (similar to HocArray)
  HocRefPStr = 11,
};
enum IteratorState {
  Begin,
  NextNotLast,
  Last
};
}  // namespace PyHoc

typedef struct {
  PyObject_HEAD Object* ho_;
  union {
    double x_;
    char* s_;
    char** pstr_;
    Object* ho_;
    double* px_;
    PyHoc::IteratorState its_;
  } u;
  Symbol* sym_;     // for functions and arrays
  void* iteritem_;  // enough info to carry out Iterator protocol
  int nindex_;      // number indices seen so far (or narg)
  int* indices_;    // one fewer than nindex_
  PyHoc::ObjectType type_;
} PyHocObject;

static PyObject* rvp_plot = NULL;
static PyObject* plotshape_plot = NULL;
static PyObject* cpp2refstr(char** cpp);
static PyObject* get_mech_object_ = NULL;
static PyObject* nrnpy_rvp_pyobj_callback = NULL;

PyTypeObject* hocobject_type;
static PyObject* hocobj_call(PyHocObject* self, PyObject* args,
                             PyObject* kwrds);

static PyObject* nrnexec(PyObject* self, PyObject* args) {
  const char* cmd;
  if (!PyArg_ParseTuple(args, "s", &cmd)) {
    return NULL;
  }
  bool b = hoc_valid_stmt(cmd, 0);
  return Py_BuildValue("i", b ? 1 : 0);
}

static PyObject* hoc_ac(PyObject* self, PyObject* args) {
  PyArg_ParseTuple(args, "|d", &hoc_ac_);
  return Py_BuildValue("d", hoc_ac_);
}

static PyMethodDef HocMethods[] = {
    {"execute", nrnexec, METH_VARARGS,
     "Execute a hoc command, return 1 on success, 0 on failure."},
    {"hoc_ac", hoc_ac, METH_VARARGS, "Get (or set) the scalar hoc_ac_."},
    {NULL, NULL, 0, NULL}};

static void hocobj_dealloc(PyHocObject* self) {
  // printf("hocobj_dealloc %p\n", self);
  if (self->ho_) {
    hoc_obj_unref(self->ho_);
  }
  if (self->type_ == PyHoc::HocRefStr && self->u.s_) {
    // delete [] self->u.s_;
    free(self->u.s_);
  }
  if (self->type_ == PyHoc::HocRefObj && self->u.ho_) {
    hoc_obj_unref(self->u.ho_);
  }
  if (self->indices_) {
    delete[] self->indices_;
  }
  if (self->type_ == PyHoc::HocRefPStr && self->u.pstr_) {
    // nothing deleted
  }
  ((PyObject*)self)->ob_type->tp_free((PyObject*)self);
}

static PyObject* hocobj_new(PyTypeObject* subtype, PyObject* args,
                            PyObject* kwds) {
  PyObject* subself;
  subself = subtype->tp_alloc(subtype, 0);
  // printf("hocobj_new %s %p\n", subtype->tp_name, subself);
  if (subself == NULL) {
    return NULL;
  }
  PyHocObject* self = (PyHocObject*)subself;
  self->ho_ = NULL;
  self->u.x_ = 0.;
  self->sym_ = NULL;
  self->indices_ = NULL;
  self->nindex_ = 0;
  self->type_ = PyHoc::HocTopLevelInterpreter;
  self->iteritem_ = 0;
  if (kwds && PyDict_Check(kwds)) {
    PyObject* base = PyDict_GetItemString(kwds, "hocbase");
    if (base) {
      int ok = 0;
      if (PyObject_TypeCheck(base, hocobject_type)) {
        PyHocObject* hbase = (PyHocObject*)base;
        if (hbase->type_ == PyHoc::HocFunction &&
            hbase->sym_->type == TEMPLATE) {
          // printf("hocobj_new base %s\n", hbase->sym_->name);
          // remove the hocbase keyword since hocobj_call only allows
          // the "sec" keyword argument
          PyDict_DelItemString(kwds, "hocbase");
          PyObject* r = hocobj_call(hbase, args, kwds);
          if (!r) {
            Py_DECREF(subself);
            return NULL;
          }
          PyHocObject* rh = (PyHocObject*)r;
          self->type_ = rh->type_;
          self->ho_ = rh->ho_;
          hoc_obj_ref(self->ho_);
          Py_DECREF(r);
          ok = 1;
        }
      }
      if (!ok) {
        Py_DECREF(subself);
        PyErr_SetString(PyExc_TypeError, "HOC base class not valid");
        return NULL;
      }
    }
  }
  return subself;
}

static int hocobj_init(PyObject* subself, PyObject* args, PyObject* kwds) {
// printf("hocobj_init %s %p\n",
// ((PyTypeObject*)PyObject_Type(subself))->tp_name, subself);
#if 0
	if (subself != NULL) {
		PyHocObject* self = (PyHocObject*)subself;
		if (self->ho_) { hoc_obj_unref(self->ho_); }
		self->ho_ = NULL;
		self->u.x_ = 0.;
		self->sym_ = NULL;
		self->indices_ = NULL;
		self->nindex_ = 0;
		self->type_ = 0;
	}
#endif
  return 0;
}

static void pyobject_in_objptr(Object** op, PyObject* po) {
  Object* o = nrnpy_pyobject_in_obj(po);
  if (*op) {
    hoc_obj_unref(*op);
  }
  *op = o;
}

static PyObject* hocobj_name(PyObject* pself, PyObject* args) {
  PyHocObject* self = (PyHocObject*)pself;
  char buf[512], *cp;
  buf[0] = '\0';
  cp = buf;
  PyObject* po;
  if (self->type_ == PyHoc::HocObject) {
    sprintf(cp, "%s", hoc_object_name(self->ho_));
  } else if (self->type_ == PyHoc::HocFunction ||
             self->type_ == PyHoc::HocArray) {
    sprintf(cp, "%s%s%s", self->ho_ ? hoc_object_name(self->ho_) : "",
            self->ho_ ? "." : "", self->sym_->name);
    if (self->type_ == PyHoc::HocArray) {
      for (int i = 0; i < self->nindex_; ++i) {
        sprintf(cp += strlen(cp), "[%d]", self->indices_[i]);
      }
      sprintf(cp += strlen(cp), "[?]");
    } else {
      sprintf(cp += strlen(cp), "()");
    }
  } else if (self->type_ == PyHoc::HocRefNum) {
    sprintf(cp, "<hoc ref value %g>", self->u.x_);
  } else if (self->type_ == PyHoc::HocRefStr) {
    sprintf(cp, "<hoc ref str \"%s\">", self->u.s_);
  } else if (self->type_ == PyHoc::HocRefPStr) {
    sprintf(cp, "<hoc ref pstr \"%s\">", *self->u.pstr_);
  } else if (self->type_ == PyHoc::HocRefObj) {
    sprintf(cp, "<hoc ref value \"%s\">", hoc_object_name(self->u.ho_));
  } else if (self->type_ == PyHoc::HocForallSectionIterator) {
    sprintf(cp, "<all section iterator next>");
  } else if (self->type_ == PyHoc::HocSectionListIterator) {
    sprintf(cp, "<SectionList iterator>");
  } else if (self->type_ == PyHoc::HocScalarPtr) {
    sprintf(cp, "<pointer to hoc scalar %g>",
            self->u.px_ ? *self->u.px_ : -1e100);
  } else if (self->type_ == PyHoc::HocArrayIncomplete) {
    sprintf(cp, "<incomplete pointer to hoc array %s>", self->sym_->name);
  } else {
    sprintf(cp, "<TopLevelHocInterpreter>");
  }
  po = Py_BuildValue("s", buf);
  return po;
}

static PyObject* hocobj_repr(PyObject* p) { return hocobj_name(p, NULL); }

static Inst* save_pc(Inst* newpc) {
  Inst* savpc = hoc_pc;
  hoc_pc = newpc;
  return savpc;
}

static int hocobj_pushargs(PyObject* args, std::vector<char*>& s2free) {
  int i, narg = PyTuple_Size(args);
  for (i = 0; i < narg; ++i) {
    PyObject* po = PyTuple_GetItem(args, i);
    // PyObject_Print(po, stdout, 0);
    // printf("  pushargs %d\n", i);
    if (nrnpy_numbercheck(po)) {
      PyObject* pn = PyNumber_Float(po);
      hoc_pushx(PyFloat_AsDouble(pn));
      Py_XDECREF(pn);
    } else if (is_python_string(po)) {
      char** ts = hoc_temp_charptr();
      Py2NRNString str(po, /* disable_release */ true);
      *ts = str.c_str();
      s2free.push_back(*ts);
      hoc_pushstr(ts);
    } else if (PyObject_IsInstance(po, (PyObject*)hocobject_type)) {
      PyHocObject* pho = (PyHocObject*)po;
      PyHoc::ObjectType tp = pho->type_;
      if (tp == PyHoc::HocObject) {
        hoc_push_object(pho->ho_);
      } else if (tp == PyHoc::HocRefNum) {
        hoc_pushpx(&pho->u.x_);
      } else if (tp == PyHoc::HocRefStr) {
        hoc_pushstr(&pho->u.s_);
      } else if (tp == PyHoc::HocRefObj) {
        hoc_pushobj(&pho->u.ho_);
      } else if (tp == PyHoc::HocScalarPtr) {
        hoc_pushpx(pho->u.px_);
      } else if (tp == PyHoc::HocRefPStr) {
        hoc_pushstr(pho->u.pstr_);
      } else {
        // make a hoc python object and push that
        Object* ob = NULL;
        pyobject_in_objptr(&ob, po);
        hoc_push_object(ob);
        hoc_obj_unref(ob);
      }
    } else {  // make a hoc PythonObject and push that?
      Object* ob = NULL;
      if (po != Py_None) {
        pyobject_in_objptr(&ob, po);
      }
      hoc_push_object(ob);
      hoc_obj_unref(ob);
    }
  }
  return narg;
}

static void hocobj_pushargs_free_strings(std::vector<char*>& s2free) {
  std::vector<char*>::iterator it = s2free.begin();
  for (; it != s2free.end(); ++it) {
    free(*it);
  }
}

static Symbol* getsym(char* name, Object* ho, int fail) {
  Symbol* sym = 0;
  if (ho) {
    sym = hoc_table_lookup(name, ho->ctemplate->symtable);
    if (!sym && strcmp(name, "delay") == 0) {
      sym = hoc_table_lookup("del", ho->ctemplate->symtable);
    } else if (!sym && ho->aliases) {
      sym = ivoc_alias_lookup(name, ho);
    }
  } else {
    sym = hoc_table_lookup(name, hoc_top_level_symlist);
    if (!sym) {
      sym = hoc_table_lookup(name, hoc_built_in_symlist);
    }
  }
  if (sym && sym->type == UNDEF) {
    sym = 0;
  }
  if (!sym && fail) {
    char e[200];
    sprintf(e, "'%s' is not a defined hoc variable name.", name);
    PyErr_SetString(PyExc_LookupError, e);
  }
  return sym;
}

// on entry the stack order is indices, args, object
// on exit all that is popped and the result is on the stack
// returns hoc_return_is_int if called on a builtin (i.e. 2 if bool, 1 if int, 0 otherwise)
static int component(PyHocObject* po) {
  Inst fc[6];
  int var_type = 0;
  hoc_return_type_code = 0;
  fc[0].sym = po->sym_;
  fc[1].i = 0;
  fc[2].i = 0;
  fc[5].i = 0;
  if (po->type_ == PyHoc::HocFunction) {
    fc[2].i = po->nindex_;
    fc[5].i = 1;
  } else if (po->type_ == PyHoc::HocArray ||
             po->type_ == PyHoc::HocArrayIncomplete) {
    fc[1].i = po->nindex_;
  }
  Object* stack_value = hoc_obj_look_inside_stack(po->nindex_);
  assert(stack_value == po->ho_);
  fc[3].i = po->ho_->ctemplate->id;
  fc[4].sym = po->sym_;
  Inst* pcsav = save_pc(fc);
  hoc_object_component();
  hoc_pc = pcsav;
  // only return a type if we're calling a built-in (these are all registered first)
  if (po->ho_->ctemplate->id <= hoc_max_builtin_class_id) {
    var_type = hoc_return_type_code;
  }
  hoc_return_type_code = 0;
  return(var_type);
}

int nrnpy_numbercheck(PyObject* po) {
  // PyNumber_Check can return 1 for things that should not reasonably
  // be cast to float but should stay as python objects.
  // e.g. numpy.arange(1,5) and 5J
  // The complexity here is partly due to SAGE having its own
  // number system, e.g. type(1) is <type 'sage.rings.integer.Integer'>
  int rval = PyNumber_Check(po);
  // but do not allow sequences
  if (rval == 1 && po->ob_type->tp_as_sequence) {
    rval = 0;
  }
  // or things that fail when float(po) fails. ARGGH! This
  // is a lot more expensive than I would like.
  if (rval == 1) {
    PyObject* tmp = PyNumber_Float(po);
    if (tmp) {
      Py_DECREF(tmp);
    } else {
      PyErr_Clear();
      rval = 0;
    }
  }
  return rval;
}

PyObject* nrnpy_ho2po(Object* o) {
  // o may be NULLobject, or encapsulate a Python object (via
  // the PythonObject class in hoc (see Py2Nrn in nrnpy_p2h.cpp),
  // or be a native hoc class instance such as Graph.
  // The return value is None, or the encapsulated PyObject or
  // an encapsulating PyHocObject
  PyObject* po;
  if (!o) {
    po = Py_BuildValue("");
  } else if (o->ctemplate->sym == nrnpy_pyobj_sym_) {
    po = nrnpy_hoc2pyobject(o);
    Py_INCREF(po);
  } else {
    po = hocobj_new(hocobject_type, 0, 0);
    ((PyHocObject*)po)->ho_ = o;
    ((PyHocObject*)po)->type_ = PyHoc::HocObject;
    hoc_obj_ref(o);
  }
  return po;
}

Object* nrnpy_po2ho(PyObject* po) {
  // po may be None, or encapsulate a hoc object (via the
  // PyHocObject, or be a native Python instance such as [1,2,3]
  // The return value is None, or the encapsulated hoc object,
  // or a hoc object of type PythonObject that encapsulates the
  // po.
  Object* o;
  if (po == Py_None) {
    o = 0;
  } else if (PyObject_TypeCheck(po, hocobject_type)) {
    PyHocObject* pho = (PyHocObject*)po;
    if (pho->type_ == PyHoc::HocObject) {
      o = pho->ho_;
      hoc_obj_ref(o);
    } else if (pho->type_ == PyHoc::HocRefObj) {
      o = pho->u.ho_;
      hoc_obj_ref(o);
    } else {
      // all the rest encapsulate
      o = nrnpy_pyobject_in_obj(po);
    }
  } else {  // even if Python string or number
    o = nrnpy_pyobject_in_obj(po);
  }
  return o;
}

PyObject* nrnpy_hoc_pop() {
  PyObject* result = 0;
  Object* ho;
  Object** d;
  switch (hoc_stack_type()) {
    case STRING:
      result = Py_BuildValue("s", *hoc_strpop());
      break;
    case VAR: {
      double* px = hoc_pxpop();
      if (px) {
        // unfortunately, this is nonsense if NMODL POINTER is pointing
        // to something other than a double.
        result = Py_BuildValue("d", *px);
      }else{
        PyErr_SetString(PyExc_AttributeError, "POINTER is NULL");
      }
    }
      break;
    case NUMBER:
      result = Py_BuildValue("d", hoc_xpop());
      break;
    case OBJECTVAR:
    case OBJECTTMP:
      d = hoc_objpop();
      ho = *d;
      // printf("Py2Nrn %p %p\n", ho->ctemplate->sym, nrnpy_pyobj_sym_);
      result = nrnpy_ho2po(ho);
      hoc_tobj_unref(d);
      break;
    default:
      printf("nrnpy_hoc_pop error: stack type = %d\n", hoc_stack_type());
  }
  return result;
}

static int set_final_from_stk(PyObject* po) {
  int err = 0;
  switch (hoc_stack_type()) {
    case STRING:
      char* s;
      if (PyArg_Parse(po, "s", &s) == 1) {
        hoc_assign_str(hoc_strpop(), s);
      } else {
        err = 1;
      }
      break;
    case VAR: {
      double x;
      double* px;
      if (PyArg_Parse(po, "d", &x) == 1) {
        px = hoc_pxpop();
        if (px) {
          // This is a future crash if NMODL POINTER is pointing
          // to something other than a double.
          *px = x;
        }else{
          PyErr_SetString(PyExc_AttributeError, "POINTER is NULL");
          return -1;
        }
      } else {
        err = 1;
      }
    }
      break;
    case OBJECTVAR:
      PyHocObject* pho;
      if (PyArg_Parse(po, "O!", hocobject_type, &pho) == 1) {
        Object** pobj = hoc_objpop();
        if (pho->sym_) {
          PyErr_SetString(PyExc_TypeError,
                          "argument cannot be a hoc object intermediate");
          return -1;
        }
        Object* ob = *pobj;
        hoc_obj_ref(pho->ho_);
        hoc_obj_unref(ob);
        *pobj = pho->ho_;
      } else {
        err = 1;
      }
      break;
    default:
      printf("set_final_from_stk() error: stack type = %d\n", hoc_stack_type());
      err = 1;
      break;
  }
  return err;
}


static void* nrnpy_hoc_int_pop() {
  return (void*) Py_BuildValue("i", (long) hoc_xpop());
}

static void* nrnpy_hoc_bool_pop() {
  return (void*) PyBool_FromLong((long) hoc_xpop());
}

static void* fcall(void* vself, void* vargs) {
  PyHocObject* self = (PyHocObject*)vself;
  if (self->ho_) {
    hoc_push_object(self->ho_);
  }
  std::vector<char*> strings_to_free;
  int narg = hocobj_pushargs((PyObject*)vargs, strings_to_free);
  int var_type;
  if (self->ho_) {
    self->nindex_ = narg;
    var_type = component(self);
    hocobj_pushargs_free_strings(strings_to_free);
    switch(var_type) {
      case 2:
        return nrnpy_hoc_bool_pop();
      case 1:
        return nrnpy_hoc_int_pop();
      default:
        return(void*) nrnpy_hoc_pop();
    }
  }
  if (self->sym_->type == BLTIN) {
    if (narg != 1) {
      hoc_execerror("must be one argument for", self->sym_->name);
    }
    double d = hoc_call_func(self->sym_, 1);
    hoc_pushx(d);
  } else if (self->sym_->type == TEMPLATE) {
    Object* ho = hoc_newobj1(self->sym_, narg);
    PyHocObject* result = (PyHocObject*)hocobj_new(hocobject_type, 0, 0);
    result->ho_ = ho;
    result->type_ = PyHoc::HocObject;
    hocobj_pushargs_free_strings(strings_to_free);
    return result;
  } else {
    HocTopContextSet Inst fc[4];
    // ugh. so a potential call of hoc_get_last_pointer_symbol will return nil.
    fc[0].in = STOP;
    fc[1].sym = self->sym_;
    fc[2].i = narg;
    fc[3].in = STOP;
    Inst* pcsav = save_pc(fc + 1);
    hoc_call();
    hoc_pc = pcsav;
    HocContextRestore
  }
  hocobj_pushargs_free_strings(strings_to_free);

  return (void*)nrnpy_hoc_pop();
}

static PyObject* curargs_;

PyObject* hocobj_call_arg(int i) {
  return PyTuple_GetItem(curargs_, i);
}

static PyObject* hocobj_call(PyHocObject* self, PyObject* args,
                             PyObject* kwrds) {

  // Hack to allow some python only methods to get the python args.
  // without losing info about type bool, int, etc.
  // eg pc.py_broadcast, pc.py_gather, pc.py_allgather
  PyObject* prevargs_ = curargs_;
  curargs_ = args;

  PyObject* section = 0;
  PyObject* result;
  if (kwrds && PyDict_Check(kwrds)) {
#if 0
		PyObject* keys = PyDict_Keys(kwrds);
		assert(PyList_Check(keys));
		int n = PyList_Size(keys);
		for (int i = 0; i < n; ++i) {
			PyObject* key = PyList_GetItem(keys, i);
			PyObject* value = PyDict_GetItem(kwrds, key);
			printf("%s %s\n", PyString_AsString(key), PyString_AsString(PyObject_Str(value)));
		}
#endif
    section = PyDict_GetItemString(kwrds, "sec");
    int num_kwargs = PyDict_Size(kwrds);
    if (num_kwargs > 1) {
      PyErr_SetString(PyExc_RuntimeError, "invalid keyword argument");
      curargs_ = prevargs_;
      return NULL;
    }
    if (section) {
      section = nrnpy_pushsec(section);
      if (!section) {
        PyErr_SetString(PyExc_TypeError, "sec is not a Section");
        curargs_ = prevargs_;
        return NULL;
      }
    } else {
      if (num_kwargs) {
        PyErr_SetString(PyExc_RuntimeError, "invalid keyword argument");
        curargs_ = prevargs_;
        return NULL;
      }
    }
  }
  if (self->type_ == PyHoc::HocTopLevelInterpreter) {
    result = nrnexec((PyObject*)self, args);
  } else if (self->type_ == PyHoc::HocFunction) {
    OcJump* oj;
    oj = new OcJump();
    if (oj) {
      result = (PyObject*)oj->fpycall(fcall, (void*)self, (void*)args);
      delete oj;
      if (result == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "hoc error");
      }
    } else {
      result = (PyObject*)fcall((void*)self, (void*)args);
    }
    hoc_unref_defer();
  } else {
    PyErr_SetString(PyExc_TypeError, "object is not callable");
    curargs_ = prevargs_;
    return NULL;
  }
  if (section) {
    nrn_popsec();
  }
  curargs_ = prevargs_;
  return result;
}

static Arrayinfo* hocobj_aray(Symbol* sym, Object* ho) {
  if (!sym->arayinfo) {
    return 0;
  }
  if (ho) {  // objectdata or not?
    int cplus = (ho->ctemplate->sym->subtype & (CPLUSOBJECT | JAVAOBJECT));
    if (cplus) {
      return sym->arayinfo;
    } else {
      return ho->u.dataspace[sym->u.oboff + 1].arayinfo;
    }
  } else {
    if (sym->type == VAR &&
        (sym->subtype == USERDOUBLE || sym->subtype == USERINT ||
         sym->subtype == USERFLOAT)) {
      return sym->arayinfo;
    } else {
      return hoc_top_level_data[sym->u.oboff + 1].arayinfo;
    }
  }
}

static PyHocObject* intermediate(PyHocObject* po, Symbol* sym, int ix) {
  PyHocObject* ponew = (PyHocObject*)hocobj_new(hocobject_type, 0, 0);
  if (po->ho_) {
    ponew->ho_ = po->ho_;
    hoc_obj_ref(po->ho_);
  }
  if (ix > -1) {  // array, increase dimension by one
    int j;
    assert(po->sym_ == sym);
    assert(po->type_ == PyHoc::HocArray ||
           po->type_ == PyHoc::HocArrayIncomplete);
    ponew->sym_ = sym;
    ponew->nindex_ = po->nindex_ + 1;
    ponew->type_ = po->type_;
    ponew->indices_ = new int[ponew->nindex_];
    for (j = 0; j < po->nindex_; ++j) {
      ponew->indices_[j] = po->indices_[j];
    }
    ponew->indices_[po->nindex_] = ix;
  } else {  // make it an array (no indices yet)
    ponew->sym_ = sym;
    ponew->type_ = PyHoc::HocArray;
  }
  return ponew;
}

// when called, nindex is 1 less than reality
static void hocobj_pushtop(PyHocObject* po, Symbol* sym, int ix) {
  int i;
  int n = po->nindex_++;
  // printf("hocobj_pushtop n=%d", po->nindex_);
  for (i = 0; i < n; ++i) {
    hoc_pushx((double)po->indices_[i]);
    // printf(" %d", po->indices_[i]);
  }
  hoc_pushx((double)ix);
  // printf(" %d\n", ix);
  if (sym) {
    hoc_pushs(sym);
  }
}

static void hocobj_objectvar(Symbol* sym) {
  Inst fc;
  fc.sym = sym;
  Inst* pcsav = save_pc(&fc);
  hoc_objectvar();
  hoc_pc = pcsav;
}

static PyObject* hocobj_getsec(Symbol* sym) {
  Inst fc;
  fc.sym = sym;
  Inst* pcsav = save_pc(&fc);
  sec_access_push();
  hoc_pc = pcsav;
  PyObject* result = nrnpy_cas(0, 0);
  nrn_popsec();
  return result;
}

// leave pointer on stack ready for get/set final
static void eval_component(PyHocObject* po, int ix) {
  int j;
  hoc_push_object(po->ho_);
  hocobj_pushtop(po, 0, ix);
  component(po);
  --po->nindex_;
}

PyObject* nrn_hocobj_ptr(double* pd) {
  PyObject* result = hocobj_new(hocobject_type, 0, 0);
  PyHocObject* po = (PyHocObject*)result;
  po->type_ = PyHoc::HocScalarPtr;
  po->u.px_ = pd;
  return result;
}

int nrn_is_hocobj_ptr(PyObject* po, double*& pd) {
  int ret = 0;
  if (PyObject_TypeCheck(po, hocobject_type)) {
    PyHocObject* hpo = (PyHocObject*)po;
    if (hpo->type_ == PyHoc::HocScalarPtr) {
      pd = hpo->u.px_;
      ret = 1;
    }
  }
  return ret;
}

static void symlist2dict(Symlist* sl, PyObject* dict) {
  PyObject* nn = Py_BuildValue("");
  for (Symbol* s = sl->first; s; s = s->next) {
    if (s->type == UNDEF) { continue; }
    if (s->cpublic == 1 || sl == hoc_built_in_symlist ||
        sl == hoc_top_level_symlist) {
      if (strcmp(s->name, "del") == 0) {
        PyDict_SetItemString(dict, "delay", nn);
      } else {
        PyDict_SetItemString(dict, s->name, nn);
      }
    }
  }
  Py_DECREF(nn);
}

static int setup_doc_system() {
  PyObject* pdoc;
  if (pfunc_get_docstring) {
    return 1;
  }
  pdoc = PyImport_ImportModule("neuron.doc");
  if (pdoc == NULL) {
    PyErr_SetString(PyExc_ImportError,
                    "Failed to import neuron.doc documentation module.");
    return 0;
  }
  pfunc_get_docstring = PyObject_GetAttrString(pdoc, "get_docstring");

  if (pfunc_get_docstring == NULL) {
    PyErr_SetString(
        PyExc_AttributeError,
        "neuron.doc module does not have attribute 'get_docstring'!");
    return 0;
  }
  return 1;
}

PyObject* toplevel_get(PyObject* subself, const char* n) {
  PyHocObject* self = (PyHocObject*)subself;
  PyObject* result = NULL;
  if (self->type_ == PyHoc::HocTopLevelInterpreter) {
    PyObject* descr = PyDict_GetItemString(topmethdict, n);
    if (descr) {
      Py_INCREF(descr);
      descrgetfunc f = descr->ob_type->tp_descr_get;
      assert(f);
      result = f(descr, subself, (PyObject*)Py_TYPE(subself));
      Py_DECREF(descr);
    }
  }
  return result;
}

// TODO: This function needs refactoring; there are too many exit points
static PyObject* hocobj_getattr(PyObject* subself, PyObject* pyname) {
  PyHocObject* self = (PyHocObject*)subself;
  if (self->type_ == PyHoc::HocObject && !self->ho_) {
    PyErr_SetString(PyExc_TypeError, "not a compound type");
    return NULL;
  }

  PyObject* result = NULL;
  int isptr = 0;
  Py2NRNString name(pyname);
  char* n = name.c_str();
  if (!n) {
    PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
    return NULL;
  }
  // printf("hocobj_getattr %s\n", n);

  Symbol* sym = getsym(n, self->ho_, 0);
  if (!sym) {
    if (self->type_ == PyHoc::HocObject &&
        self->ho_->ctemplate->sym == nrnpy_pyobj_sym_) {
      PyObject* p = nrnpy_hoc2pyobject(self->ho_);
      return PyObject_GenericGetAttr(p, pyname);
    }
    if (self->type_ == PyHoc::HocTopLevelInterpreter) {
      result = toplevel_get(subself, n);
      if (result) {
        return result;
      }
    }
    if (strcmp(n, "__dict__") == 0) {
      // all the public names
      Symlist* sl = 0;
      if (self->ho_) {
        sl = self->ho_->ctemplate->symtable;
      } else if (self->sym_ && self->sym_->type == TEMPLATE) {
        sl = self->sym_->u.ctemplate->symtable;
      }
      PyObject* dict = PyDict_New();
      if (sl) {
        symlist2dict(sl, dict);
      } else {
        symlist2dict(hoc_built_in_symlist, dict);
        symlist2dict(hoc_top_level_symlist, dict);
        add2topdict(dict);
      }

      // Is the self->ho_ a Vector?  If so, add the __array_interface__ symbol

      if (is_obj_type(self->ho_, "Vector")) {
        PyDict_SetItemString(dict, "__array_interface__", Py_None);
      } else if (is_obj_type(self->ho_, "RangeVarPlot") || is_obj_type(self->ho_, "PlotShape")) {
        PyDict_SetItemString(dict, "plot", Py_None);
      }
      return dict;
    } else if (strncmp(n, "_ref_", 5) == 0) {
      if (self->type_ > PyHoc::HocObject) {
        PyErr_SetString(PyExc_TypeError,
                        "not a HocTopLevelInterpreter or HocObject");
        return NULL;
      }
      sym = getsym(n + 5, self->ho_, 0);
      if (!sym) {
        return PyObject_GenericGetAttr((PyObject*)subself, pyname);
      }
      if (sym->type == STRING) {
        Objectdata* od = hoc_objectdata_save();
        if (self->type_ == PyHoc::HocTopLevelInterpreter) {
          hoc_objectdata = hoc_top_level_data;
        }else if (self->type_ == PyHoc::HocObject && !self->ho_->ctemplate->constructor) {
          hoc_objectdata = self->ho_->u.dataspace;
        }else{
          hoc_objectdata = hoc_objectdata_restore(od);
          assert(0);
          return NULL;
        }
        char** cpp = OPSTR(sym);
        hoc_objectdata = hoc_objectdata_restore(od);
        result = cpp2refstr(cpp);
        return result;
      }else if (sym->type != VAR && sym->type != RANGEVAR && sym->type != VARALIAS) {
        char buf[200];
        sprintf(buf,
                "Hoc pointer error, %s is not a hoc variable or range variable or strdef",
                sym->name);
        PyErr_SetString(PyExc_TypeError, buf);
        return NULL;
      }else{
        isptr = 1;
      }
    } else if (is_obj_type(self->ho_, "Vector") &&
               strcmp(n, "__array_interface__") == 0) {
      // return __array_interface__
      // printf("building array interface\n");
      Vect* v = (Vect*)self->ho_->u.this_pointer;
      int size = v->capacity();
      double* x = vector_vec(v);

      return Py_BuildValue("{s:(i),s:s,s:i,s:(N,O)}", "shape", size, "typestr",
                           array_interface_typestr, "version", 3, "data",
                           PyLong_FromVoidPtr(x), Py_True);

    } else if (is_obj_type(self->ho_, "RangeVarPlot") && strcmp(n, "plot") == 0) {
      return PyObject_CallFunctionObjArgs(rvp_plot, (PyObject*) self, NULL);
    } else if (is_obj_type(self->ho_, "PlotShape") && strcmp(n, "plot") == 0) {
      return PyObject_CallFunctionObjArgs(plotshape_plot, (PyObject*) self, NULL);
    } else if (strcmp(n, "__doc__") == 0) {
      if (setup_doc_system()) {
        PyObject* docobj = NULL;
        if (self->ho_) {
          docobj = Py_BuildValue("s s", self->ho_->ctemplate->sym->name,
                                 self->sym_ ? self->sym_->name : "");
        } else if (self->sym_) {
          // Symbol
          docobj = Py_BuildValue("s s", "", self->sym_->name);
        } else {
          // Base HocObject

          docobj = Py_BuildValue("s s", "", "");
        }

        result = PyObject_CallObject(pfunc_get_docstring, docobj);
        Py_DECREF(docobj);
        return result;
      } else {
        return NULL;
      }
    } else if (self->type_ == PyHoc::HocTopLevelInterpreter &&
               strncmp(n, "__nrnsec_0x", 11) == 0) {
      Section* sec = (Section*)hoc_sec_internal_name2ptr(n, 0);
      if (sec == NULL) {
        PyErr_SetString(PyExc_NameError, n);
      } else if (sec && sec->prop && sec->prop->dparam[PROP_PY_INDEX]._pvoid) {
        result = (PyObject*)sec->prop->dparam[PROP_PY_INDEX]._pvoid;
        Py_INCREF(result);
      } else {
        nrn_pushsec(sec);
        result = nrnpy_cas(NULL, NULL);
        nrn_popsec();
      }
      return result;
    } else if (self->type_ == PyHoc::HocTopLevelInterpreter &&
               strncmp(n, "__pysec_", 8) == 0) {
      Section* sec = (Section*)hoc_pysec_name2ptr(n, 0);
      if (sec == NULL) {
        PyErr_SetString(PyExc_NameError, n);
      } else if (sec && sec->prop && sec->prop->dparam[PROP_PY_INDEX]._pvoid) {
        result = (PyObject*)sec->prop->dparam[PROP_PY_INDEX]._pvoid;
        Py_INCREF(result);
      } else {
        nrn_pushsec(sec);
        result = nrnpy_cas(NULL, NULL);
        nrn_popsec();
      }
      return result;
    } else {
      // ipython wants to know if there is a __getitem__
      // even though it does not use it.
      return PyObject_GenericGetAttr((PyObject*)subself, pyname);
    }
  }
  // printf("%s type=%d nindex=%d %s\n", self->sym_?self->sym_->name:"noname",
  // self->type_, self->nindex_, sym->name);
  // no hoc component for a hoc function
  // ie the sym has to be a component for the object returned by the function
  if (self->type_ == PyHoc::HocFunction) {
    PyErr_SetString(
        PyExc_TypeError,
        "No hoc method for a callable. Missing parentheses before the '.'?");
    return NULL;
  }
  if (self->type_ == PyHoc::HocArray) {
    PyErr_SetString(PyExc_TypeError, "Missing array index");
    return NULL;
  }
  if (self->ho_) {  // use the component fork.
    result = hocobj_new(hocobject_type, 0, 0);
    PyHocObject* po = (PyHocObject*)result;
    po->ho_ = self->ho_;
    hoc_obj_ref(po->ho_);
    po->sym_ = sym;
    // evaluation deferred unless VAR,STRING,OBJECTVAR and not
    // an array
    int t = sym->type;
    if (t == VAR || t == STRING || t == OBJECTVAR || t == RANGEVAR ||
        t == SECTION || t == SECTIONREF || t == VARALIAS || t == OBJECTALIAS) {
      if (sym != nrn_child_sym && !ISARRAY(sym)) {
        hoc_push_object(po->ho_);
        nrn_inpython_ = 1;
        component(po);
        if (nrn_inpython_ == 2) {  // error in component
          nrn_inpython_ = 0;
          PyErr_SetString(PyExc_TypeError, "No value");
          Py_DECREF(po);
          return NULL;
        }
        nrn_inpython_ = 0;
        Py_DECREF(po);
        if (t == SECTION || t == SECTIONREF) {
          section_object_seen = 0;
          result = nrnpy_cas(0, 0);
          nrn_popsec();
          return result;
        } else {
          if (isptr) {
            return nrn_hocobj_ptr(hoc_pxpop());
          } else {
            return nrnpy_hoc_pop();
          }
        }
      } else {
        if (isptr) {
          po->type_ = PyHoc::HocArrayIncomplete;
        } else {
          po->type_ = PyHoc::HocArray;
        }
        return result;
      }
    } else {
      po->type_ = PyHoc::HocFunction;
      return result;
    }
  }
  // top level interpreter fork
  HocTopContextSet switch (sym->type) {
    case VAR:  // double*
      if (!ISARRAY(sym)) {
        if (sym->subtype == USERINT) {
          result = Py_BuildValue("i", *(sym->u.pvalint));
          break;
        }
        if (sym->subtype == USERPROPERTY) {
          if (!nrn_noerr_access()) {
            PyErr_SetString(PyExc_TypeError, "Section access unspecified");
            break;
          }
          if (!isptr) {
            if (sym->u.rng.type == CABLESECTION) {
              result = Py_BuildValue("d", cable_prop_eval(sym));
            }else{
              result = Py_BuildValue("i", int(cable_prop_eval(sym)));
            }
            break;
          }else if (sym->u.rng.type != CABLESECTION) {
            PyErr_SetString(PyExc_TypeError, "Cannot be a reference");
            break;
          }
        }
        hoc_pushs(sym);
        hoc_evalpointer();
        if (isptr) {
          result = nrn_hocobj_ptr(hoc_pxpop());
        } else {
          result = Py_BuildValue("d", *hoc_pxpop());
        }
      } else {
        result = (PyObject*)intermediate(self, sym, -1);
        if (isptr) {
          ((PyHocObject*)result)->type_ = PyHoc::HocArrayIncomplete;
        } else {
        }
      }
      break;
    case STRING:  // char*
    {
      Inst fc, *pcsav;
      fc.sym = sym;
      pcsav = save_pc(&fc);
      hoc_push_string();
      hoc_pc = pcsav;
      result = Py_BuildValue("s", *hoc_strpop());
    } break;
    case OBJECTVAR:  // Object*
      if (!ISARRAY(sym)) {
        Inst fc, *pcsav;
        fc.sym = sym;
        pcsav = save_pc(&fc);
        hoc_objectvar();
        hoc_pc = pcsav;
        Object* ho = *hoc_objpop();
        result = nrnpy_ho2po(ho);
      } else {
        result = (PyObject*)intermediate(self, sym, -1);
      }
      break;
    case SECTION:
      if (!ISARRAY(sym)) {
        result = hocobj_getsec(sym);
      } else {
        result = (PyObject*)intermediate(self, sym, -1);
      }
      break;
    case PROCEDURE:
    case FUNCTION:
    case FUN_BLTIN:
    case BLTIN:
    case HOCOBJFUNCTION:
    case STRINGFUNC:
    case TEMPLATE:
    case OBJECTFUNC: {
      result = hocobj_new(hocobject_type, 0, 0);
      PyHocObject* po = (PyHocObject*)result;
      if (self->ho_) {
        po->ho_ = self->ho_;
        hoc_obj_ref(po->ho_);
      }
      po->type_ = PyHoc::HocFunction;
      po->sym_ = sym;
      // printf("function %s\n", po->sym_->name);
      break;
    }
    case SETPOINTERKEYWORD:
      result = toplevel_get(subself, n);
      break;
    default:  // otherwise
    {
      if (PyDict_GetItemString(pmech_types, n)) {
        result = PyObject_CallFunction(get_mech_object_, "s", n);
        break;
      } else if (PyDict_GetItemString(rangevars_, n)) {
        PyErr_Format(PyExc_TypeError, "Cannot access %s directly; it is a range variable and may be accessed via a section or segment.", n);
      } else {
        PyErr_Format(PyExc_TypeError, "Cannot access %s (NEURON type %d) directly.", n, sym->type);
        break;
      }
    }
  }
  HocContextRestore return result;
}

static PyObject* hocobj_baseattr(PyObject* subself, PyObject* args) {
  PyObject* name;
  if (!PyArg_ParseTuple(args, "O", &name)) {
    return NULL;
  }
  return hocobj_getattr(subself, name);
}

static int refuse_to_look;
static PyObject* hocobj_getattro(PyObject* subself, PyObject* name) {
  PyObject* result = 0;
  if ((PyTypeObject*)PyObject_Type(subself) != hocobject_type) {
    // printf("try generic %s\n", PyString_AsString(name));
    result = PyObject_GenericGetAttr(subself, name);
    if (result) {
      // printf("found generic %s\n", PyString_AsString(name));
      return result;
    } else {
      PyErr_Clear();
    }
  }
  if (!refuse_to_look) {
    result = hocobj_getattr(subself, name);
  }
  return result;
}

static int hocobj_setattro(PyObject* subself, PyObject* pyname,
                           PyObject* value) {
  int err = 0;
  Inst* pcsav;
  Inst fc;

  int issub = ((PyTypeObject*)PyObject_Type(subself) != hocobject_type);
  if (issub) {
    // printf("try hasattr %s\n", PyString_AsString(name));
    refuse_to_look = 1;
    if (PyObject_HasAttr(subself, pyname)) {
      refuse_to_look = 0;
      // printf("found hasattr for %s\n", PyString_AsString(name));
      return PyObject_GenericSetAttr(subself, pyname, value);
    }
    refuse_to_look = 0;
  }

  PyHocObject* self = (PyHocObject*)subself;
  PyHocObject* po;

  if (self->type_ == PyHoc::HocObject && !self->ho_) {
    return 1;
  }
  Py2NRNString name(pyname);
  char* n = name.c_str();
  if (!n) {
    PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
    return -1;
  }
  // printf("hocobj_setattro %s\n", n);
  Symbol* sym = getsym(n, self->ho_, 0);
  if (!sym) {
    if (issub) {
      return PyObject_GenericSetAttr(subself, pyname, value);
    } else if (!sym && self->type_ == PyHoc::HocObject &&
               self->ho_->ctemplate->sym == nrnpy_pyobj_sym_) {
      PyObject* p = nrnpy_hoc2pyobject(self->ho_);
      return PyObject_GenericSetAttr(p, pyname, value);
    }else if (strncmp(n, "_ref_", 5) == 0) {
        extern int nrn_pointer_assign(Prop*, Symbol*, PyObject*);
        Symbol* rvsym = getsym(n+5, self->ho_, 0);
        if (rvsym && rvsym->type == RANGEVAR) {
          Prop* prop = ob2pntproc_0(self->ho_)->prop;
          if (!prop) {
            PyErr_SetString(PyExc_TypeError, "Point_process not located in a section");
            return -1;
          }
          err = nrn_pointer_assign(prop, rvsym, value);
          return err;
        }
        sym = getsym(n, self->ho_, 1);
    } else {
      sym = getsym(n, self->ho_, 1);
    }
  }
  if (!sym) {
    return -1;
  }
  if (self->ho_) {  // use the component fork.
    PyObject* result = hocobj_new(hocobject_type, 0, 0);
    po = (PyHocObject*)result;
    po->ho_ = self->ho_;
    hoc_obj_ref(po->ho_);
    po->sym_ = sym;
    // evaluation deferred unless VAR,STRING,OBJECTVAR and not
    // an array
    int t = sym->type;
    if (t == VAR || t == STRING || t == OBJECTVAR || t == RANGEVAR ||
        t == VARALIAS || t == OBJECTALIAS) {
      if (!ISARRAY(sym)) {
        hoc_push_object(po->ho_);
        nrn_inpython_ = 1;
        component(po);
        if (nrn_inpython_ == 2) {  // error in component
          nrn_inpython_ = 0;
          PyErr_SetString(PyExc_TypeError, "No value");
          return -1;
        }
        Py_DECREF(po);
        return set_final_from_stk(value);
      } else {
        char e[200];
        sprintf(e, "'%s' requires subscript for assignment", n);
        PyErr_SetString(PyExc_TypeError, e);
        return -1;
      }
    } else {
      PyErr_SetString(PyExc_TypeError, "not assignable");
      return -1;
    }
  }
  HocTopContextSet switch (sym->type) {
    case VAR:  // double*
      if (ISARRAY(sym)) {
        PyErr_SetString(PyExc_TypeError, "wrong number of subscripts");
        err = -1;
      } else {
        if (sym->subtype == USERINT) {
          err = PyArg_Parse(value, "i", sym->u.pvalint) == 0;
        } else if (sym->subtype == USERPROPERTY) {
          if (!nrn_noerr_access()) {
            PyErr_SetString(PyExc_TypeError, "Section access unspecified");
            err = -1;
            break;
          }
          double x;
          if (sym->u.rng.type != CABLESECTION) {
            int i;
            if (PyArg_Parse(value, "i", &i) != 0 && i > 0 && i <= 32767) {
              x = double(i);
            }else{
              PyErr_SetString(PyExc_ValueError, "nseg must be an integer in range 1 to 32767");
              err = -1;
            }
          } else {
            err = PyArg_Parse(value, "d", &x) == 0;
          }
          if (!err) {
            cable_prop_assign(sym, &x, 0);
          }
        } else {
          hoc_pushs(sym);
          hoc_evalpointer();
          err = PyArg_Parse(value, "d", hoc_pxpop()) == 0;
          if (!err && sym->subtype == DYNAMICUNITS) {
            char mes[100];
            sprintf(mes, "Assignment to %s value of physical constant %s",
              _nrnunit_use_legacy_ ? "legacy" : "modern",
              sym->name);
            err = PyErr_WarnEx(PyExc_Warning, mes, 1);
          }
        }
      }
      break;
    case STRING:  // char*
      fc.sym = sym;
      pcsav = save_pc(&fc);
      hoc_push_string();
      hoc_pc = pcsav;
      char* s;
      if (PyArg_Parse(value, "s", &s) == 1) {
        hoc_assign_str(hoc_strpop(), s);
      } else {
        err = 1;
      }
      break;
    case OBJECTVAR:  // Object*
    {
      hocobj_objectvar(sym);
      Object** op;
      op = hoc_objpop();
      PyObject* po;
      PyHocObject* pho;
      if (PyArg_Parse(value, "O", &po) == 1) {
        if (po == Py_None) {
          hoc_obj_unref(*op);
          *op = 0;
        } else if (PyObject_TypeCheck(po, hocobject_type)) {
          pho = (PyHocObject*)po;
          if (pho->sym_) {
            PyErr_SetString(PyExc_TypeError,
                            "argument cannot be a hoc object intermediate");
            err = -1;
          } else {
            hoc_obj_ref(pho->ho_);
            hoc_obj_unref(*op);
            *op = pho->ho_;
          }
        } else {  // it is a PythonObject in hoc
          pyobject_in_objptr(op, po);
        }
      } else {
        err = 1;
      }
      break;
    }
    default:
      PyErr_SetString(PyExc_TypeError, "not assignable");
      err = -1;
      break;
  }
  HocContextRestore return err;
}

static Symbol* sym_vec_x;
static Symbol* sym_mat_x;
static Symbol* sym_netcon_weight;

static int araylen(Arrayinfo* a, PyHocObject* po) {
  assert(a->nsub > po->nindex_);
  int n = 0;
  // Hoc Vector and Matrix are special cases because the sub[]
  // do not get filled in til just before hoc_araypt is called.
  // at least check the vector
  if (po->sym_ == sym_vec_x) {
    n = vector_capacity((IvocVect*)po->ho_->u.this_pointer);
  } else if (po->sym_ == sym_netcon_weight) {
    double* w;
    n = nrn_netcon_weight(po->ho_->u.this_pointer, &w);
  } else if (po->sym_ == nrn_child_sym) {
    n = nrn_secref_nchild((Section*)po->ho_->u.this_pointer);
  } else if (po->sym_ == sym_mat_x) {
    n = nrn_matrix_dim(po->ho_->u.this_pointer, po->nindex_);
  } else {
    n = a->sub[po->nindex_];
  }
  return n;
}

static int araychk(Arrayinfo* a, PyHocObject* po, int ix) {
  int n = araylen(a, po);
  if (ix < 0 || n <= ix) {
    // printf("ix=%d nsub=%d nindex=%d sub[nindex]=%d\n", ix, a->nsub,
    // po->nindex_, a->sub[po->nindex_]);
    char e[200];
    sprintf(e, "%s%s%s", po->ho_ ? hoc_object_name(po->ho_) : "",
            (po->ho_ && po->sym_) ? "." : "", po->sym_ ? po->sym_->name : "");
    PyErr_SetString(PyExc_IndexError, e);
    return -1;
  }
  return 0;
}

static Py_ssize_t hocobj_len(PyObject* self) {
  PyHocObject* po = (PyHocObject*)self;
  if (po->type_ == PyHoc::HocObject) {
    if (po->ho_->ctemplate == hoc_vec_template_) {
      return vector_capacity((Vect*)po->ho_->u.this_pointer);
    } else if (po->ho_->ctemplate == hoc_list_template_) {
      return ivoc_list_count(po->ho_);
    } else if (po->ho_->ctemplate == hoc_sectionlist_template_) {
      PyErr_SetString(PyExc_TypeError, "hoc.SectionList has no len()");
      return -1;
    }
  } else if (po->type_ == PyHoc::HocArray) {
    Arrayinfo* a = hocobj_aray(po->sym_, po->ho_);
    return araylen(a, po);
  } else if (po->sym_ && po->sym_->type == TEMPLATE) {
    return po->sym_->u.ctemplate->count;
  } else if (po->type_ == PyHoc::HocForallSectionIterator) {
    PyErr_SetString(PyExc_TypeError, "hoc all section iterator() has no len()");
    return -1;
  } else if (po->type_ == PyHoc::HocSectionListIterator) {
    PyErr_SetString(PyExc_TypeError, "hoc SectionList iterator() has no len()");
    return -1;
  }
  PyErr_SetString(PyExc_TypeError, "Most HocObject have no len()");
  return -1;
}

static int hocobj_nonzero(PyObject* self) {
  // printf("hocobj_nonzero\n");
  PyHocObject* po = (PyHocObject*)self;
  int b = 1;
  if (po->type_ == PyHoc::HocObject) {
    if (po->ho_->ctemplate == hoc_vec_template_) {
      b = vector_capacity((Vect*)po->ho_->u.this_pointer) > 0;
    } else if (po->ho_->ctemplate == hoc_list_template_) {
      b = ivoc_list_count(po->ho_) > 0;
    }
  } else if (po->type_ == PyHoc::HocArray) {
    Arrayinfo* a = hocobj_aray(po->sym_, po->ho_);
    b = araylen(a, po) > 0;
  } else if (po->sym_ && po->sym_->type == TEMPLATE) {
    b = 1; // prior behavior: po->sym_->u.ctemplate->count > 0;
  }
  return b;
}

PyObject* nrnpy_forall(PyObject* self, PyObject* args) {
  PyObject* po = hocobj_new(hocobject_type, 0, 0);
  PyHocObject* pho = (PyHocObject*)po;
  pho->type_ = PyHoc::HocForallSectionIterator;
  pho->u.its_ = PyHoc::Begin;
  pho->iteritem_ = section_list;
  return po;
}

static PyObject* hocobj_iter(PyObject* self) {
  //	printf("hocobj_iter %p\n", self);
  PyHocObject* po = (PyHocObject*)self;
  if (po->type_ == PyHoc::HocObject) {
    if (po->ho_->ctemplate == hoc_vec_template_) {
      return PySeqIter_New(self);
    } else if (po->ho_->ctemplate == hoc_list_template_) {
      return PySeqIter_New(self);
    } else if (po->ho_->ctemplate == hoc_sectionlist_template_) {
      // need a clone of self so nested loops do not share iteritem_
      PyObject* po2 = nrnpy_ho2po(po->ho_);
      PyHocObject* pho2 = (PyHocObject*)po2;
      pho2->type_ = PyHoc::HocSectionListIterator;
      pho2->u.its_ = PyHoc::Begin;
      pho2->iteritem_ = ((hoc_Item*)po->ho_->u.this_pointer);
      return po2;
    }
  } else if (po->type_ == PyHoc::HocForallSectionIterator) {
    po->iteritem_ = section_list;
    po->u.its_ = PyHoc::Begin;
    Py_INCREF(self);
    return self;
  } else if (po->type_ == PyHoc::HocArray) {
    return PySeqIter_New(self);
  } else if (po->sym_ && po->sym_->type == TEMPLATE) {
    po->iteritem_ = po->sym_->u.ctemplate->olist->next;
    Py_INCREF(self);
    return self;
  }
  PyErr_SetString(PyExc_TypeError, "Not an iterable HocObject");
  return NULL;
}

static hoc_Item* next_valid_secitem(hoc_Item* q, hoc_Item* ql) {
  hoc_Item* nextnext;
  hoc_Item* next;
  for ( next = q->next; next != ql; next = nextnext) {
    nextnext = next->next;
    Section* sec = next->element.sec;
    if (sec->prop) { // valid
      break;
    }
    hoc_l_delete(next);
    section_unref(sec);
  }
  return next;
}

static PyObject* iternext_sl(PyHocObject* po, hoc_Item* ql) {
  // Note that the longstanding behavior of changing the currently
  // accessed section during iteration no longer takes place because
  // we cannot guarantee that an iterate will complete with state
  // PyHoc::Last and so the previous Section would have been left on the
  // hoc section stack.

  // Primarily the Section is pushed and the currently accessed python
  // Section is returned during sequential iteration over the list ql.
  // On re-entry here, the previous Section is popped.
  // The complexity is due to the possibility that the returned nrn_Section
  // may be deleted with h.delete_section(sec=nrn_Section). This would
  // invalidate the po->iteritem_ (point to freed memory) if it were
  // the iteritem of the current Section. Thus we choose to store
  // the next iteritem pointer in the list. Although not 100% safe, since
  // the user body of the iterator is allowed to delete_section an arbitary
  // subset of sections, the obvious work around, making a copy of ql,
  // is considered not worth it.

  // In this implementation, the first call to internext_sl starts out
  // in state PyHoc::Begin with po->iteritem_ == ql. If there is no valid
  // item, it sets po->iteritem_ = NULL and returns NULL. If there is
  // a valid item, it moves to state PyHoc::Last or PyHoc::NextNotLast
  // depending on whether there is a valid secitem following the current
  // item. The current section is pushed and currently accessed python
  // section is returned.

  // Thereafter, re-entry with po->iteritem_ == NULL, immediately
  // returns NULL.

  // Re-entry in state PyHoc::NextNotLast, pops the previously pushed Section,
  // sets sec to the current Section from po->iteritem_, and
  // sets po->iteritem_ to the next_valid_section. If there is no next_valid
  // section, then move to state PyHoc::Last.
  // Push the current section and return currently accessed python Section.

  // Re-entry in state PyHoc::Last just pops the current section, sets
  // po->iteritem_ = NULL, and returns NULL.

  if (po->iteritem_ == NULL) {
    return NULL;
  }

  if (po->u.its_ == PyHoc::Begin) {
    assert(po->iteritem_ == ql);
    hoc_Item* curitem = next_valid_secitem((hoc_Item*)(po->iteritem_), ql);
    if (curitem != ql) { // typical case, a valid current item
      Section* sec = curitem->element.sec;
      assert(sec->prop);
      // sec could be delete_section before return to internext.sl
      // which would invalidate curitem.
      // Not perfectly safe but leave po->iteritem_ as next valid
      // secitem after curitem.
      po->iteritem_ = next_valid_secitem(curitem, ql);
      if (po->iteritem_ == ql) {
        po->u.its_ = PyHoc::Last;
      }else{
        po->u.its_ = PyHoc::NextNotLast;
      }
      return (PyObject*)newpysechelp(sec);
    }else{ // no valid current item so stop
      po->iteritem_ = NULL;
      return NULL;
    }
  } else if (po->u.its_ == PyHoc::NextNotLast) {
    // it would be a bug if po->iteritem_ (now the curitem) has been delete_section
    Section* sec = ((hoc_Item*)(po->iteritem_))->element.sec;
    if (!sec->prop) {
      // Handle an edge case where sec is a Python
      // Section and happens to have had as its only reference the
      // iteration variable on the previous iteration.
      // i.e. when sec is returned from the previous iteration, it will
      // be referenced by the iteration variable and the previous reference
      // will go to 0, thus invalidating po->iteritem_->element.sec->prop
      po->iteritem_ = next_valid_secitem((hoc_Item*)(po->iteritem_), ql);
      if (po->iteritem_ == ql) {
        po->u.its_ = PyHoc::Last;
        po->iteritem_ = NULL;
        return NULL;
      }else{
        sec = ((hoc_Item*)(po->iteritem_))->element.sec;
      }
    }
    assert(sec->prop);
    po->iteritem_ = next_valid_secitem((hoc_Item*)(po->iteritem_), ql);
    if (po->iteritem_ == ql) {
      po->u.its_ = PyHoc::Last;
    }
    return (PyObject*)newpysechelp(sec);
  } else if (po->u.its_ == PyHoc::Last) {
    po->iteritem_ = NULL;
    return NULL;
  }
  return NULL; // never get here as po->u.its_ is always a defined state.
}

static PyObject* hocobj_iternext(PyObject* self) {
  // printf("hocobj_iternext %p\n", self);
  PyHocObject* po = (PyHocObject*)self;
  if (po->type_ == PyHoc::HocSectionListIterator) {
    hoc_Item* ql = (hoc_Item*)po->ho_->u.this_pointer;
    return iternext_sl(po, ql);
  } else if (po->type_ == PyHoc::HocForallSectionIterator) {
    return iternext_sl(po, section_list);
  } else if (po->sym_->type == TEMPLATE) {
    hoc_Item* q = (hoc_Item*)po->iteritem_;
    if (q != po->sym_->u.ctemplate->olist) {
      po->iteritem_ = q->next;
      return nrnpy_ho2po(OBJ(q));
    }
  }
  return NULL;
}

/*
Had better be an array. But the same ambiguity as with getattro
in that we may return the final value or an intermediate (in the
case where there is more than one dimension.) At least for now we
only have to handle the OBJECTVAR and VAR case as a component and
at the top level.
*/
static PyObject* hocobj_getitem(PyObject* self, Py_ssize_t ix) {
  PyObject* result = NULL;
  PyHocObject* po = (PyHocObject*)self;
  if (po->type_ > PyHoc::HocArray && po->type_ != PyHoc::HocArrayIncomplete) {
    if (ix != 0 && po->type_ != PyHoc::HocScalarPtr) {
      PyErr_SetString(PyExc_IndexError, "index for hoc ref must be 0");
      return NULL;
    }
    if (po->type_ == PyHoc::HocScalarPtr) {
      result = Py_BuildValue("d", po->u.px_[ix]);
    } else if (po->type_ == PyHoc::HocRefNum) {
      result = Py_BuildValue("d", po->u.x_);
    } else if (po->type_ == PyHoc::HocRefStr) {
      result = Py_BuildValue("s", po->u.s_);
    } else if (po->type_ == PyHoc::HocRefPStr) {
      result = Py_BuildValue("s", *po->u.pstr_);
    } else {
      result = nrnpy_ho2po(po->u.ho_);
    }
    return result;
  }
  if (po->type_ == PyHoc::HocObject) {  // might be in an iterator context
    if (po->ho_->ctemplate == hoc_vec_template_) {
      Vect* hv = (Vect*)po->ho_->u.this_pointer;
      if (ix < 0 || ix >= vector_capacity(hv)) {
        char e[200];
        sprintf(e, "%s", hoc_object_name(po->ho_));
        PyErr_SetString(PyExc_IndexError, e);
        return NULL;
      } else {
        return PyFloat_FromDouble(vector_vec(hv)[ix]);
      }
    } else if (po->ho_->ctemplate == hoc_list_template_) {
      OcList* hl = (OcList*)po->ho_->u.this_pointer;
      if (ix < 0 || ix >= hl->count()) {
        char e[200];
        sprintf(e, "%s", hoc_object_name(po->ho_));
        PyErr_SetString(PyExc_IndexError, e);
        return NULL;
      } else {
        return nrnpy_ho2po(hl->object(ix));
      }
    } else {
      PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
      return NULL;
    }
  }
  if (!po->sym_) {
    // printf("unsubscriptable %s %d type=%d\n", hoc_object_name(po->ho_), ix,
    // po->type_);
    PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
    return NULL;
  } else if (po->sym_->type == TEMPLATE) {
    hoc_Item* q, * ql = po->sym_->u.ctemplate->olist;
    Object* ob;
    ITERATE(q, ql) {
      ob = OBJ(q);
      if (ob->index == ix) {
        return nrnpy_ho2po(ob);
      }
    }
    char e[200];
    sprintf(e, "%s[%ld] instance does not exist", po->sym_->name, ix);
    PyErr_SetString(PyExc_IndexError, e);
    return NULL;
  }
  if (po->type_ != PyHoc::HocArray && po->type_ != PyHoc::HocArrayIncomplete) {
    char e[200];
    sprintf(e, "unsubscriptable object, type %d\n", po->type_);
    PyErr_SetString(PyExc_TypeError, e);
    return NULL;
  }
  Arrayinfo* a = hocobj_aray(po->sym_, po->ho_);
  if (araychk(a, po, ix)) {
    return NULL;
  }
  if (a->nsub - 1 > po->nindex_) {  // another intermediate
    PyHocObject* ponew = intermediate(po, po->sym_, ix);
    result = (PyObject*)ponew;
  } else {  // ready to evaluate
    if (po->ho_) {
      eval_component(po, ix);
      if (po->sym_->type == SECTION || po->sym_->type == SECTIONREF) {
        section_object_seen = 0;
        result = nrnpy_cas(0, 0);
        nrn_popsec();
        return result;
      } else {
        if (po->type_ == PyHoc::HocArrayIncomplete) {
          result = nrn_hocobj_ptr(hoc_pxpop());
        } else {
          result = nrnpy_hoc_pop();
        }
      }
    } else {  // must be a top level intermediate
      HocTopContextSet switch (po->sym_->type) {
        case VAR:
          hocobj_pushtop(po, po->sym_, ix);
          hoc_evalpointer();
          --po->nindex_;
          if (po->type_ == PyHoc::HocArrayIncomplete) {
            assert(!po->u.px_);
            result = nrn_hocobj_ptr(hoc_pxpop());
          } else {
            result = Py_BuildValue("d", *hoc_pxpop());
          }
          break;
        case OBJECTVAR:
          hocobj_pushtop(po, 0, ix);
          hocobj_objectvar(po->sym_);
          --po->nindex_;
          result = nrnpy_ho2po(*hoc_objpop());
          break;
        case SECTION:
          hocobj_pushtop(po, 0, ix);
          result = hocobj_getsec(po->sym_);
          --po->nindex_;
          break;
      }
      HocContextRestore
    }
  }
  return result;
}

static int hocobj_setitem(PyObject* self, Py_ssize_t i, PyObject* arg) {
  // printf("hocobj_setitem %d\n", i);
  int err = -1;
  PyHocObject* po = (PyHocObject*)self;
  if (po->type_ > PyHoc::HocArray) {
    if (po->type_ == PyHoc::HocArrayIncomplete) {
      PyErr_SetString(PyExc_TypeError, "incomplete hoc pointer");
      return -1;
    }
    if (i != 0 && po->type_ != PyHoc::HocScalarPtr) {
      PyErr_SetString(PyExc_IndexError, "index for hoc ref must be 0");
      return -1;
    }
    if (po->type_ == PyHoc::HocScalarPtr) {
      PyArg_Parse(arg, "d", po->u.px_ + i);
    } else if (po->type_ == PyHoc::HocRefNum) {
      PyArg_Parse(arg, "d", &po->u.x_);
    } else if (po->type_ == PyHoc::HocRefStr) {
      char* ts;
      PyArg_Parse(arg, "s", &ts);
      hoc_assign_str(&po->u.s_, ts);
    } else if (po->type_ == PyHoc::HocRefPStr) {
      char* ts;
      PyArg_Parse(arg, "s", &ts);
      hoc_assign_str(po->u.pstr_, ts);
    } else {
      PyObject* tp;
      PyArg_Parse(arg, "O", &tp);
      po->u.ho_ = nrnpy_po2ho(tp);
    }
    return 0;
  }
  if (po->ho_) {
    if (po->ho_->ctemplate == hoc_vec_template_) {
      Vect* vec = (Vect*)po->ho_->u.this_pointer;
      int vec_size = vector_capacity(vec);
      // allow Python style negative indices
      if (i < 0) {
        i += vec_size;
      }
      if (i >= vec_size || i < 0) {
        PyErr_SetString(PyExc_IndexError, "index out of bounds");
        return -1;
      }
      PyArg_Parse(arg, "d", vector_vec(vec) + i);
      return 0;
    }
  }
  if (!po->sym_ || po->type_ != PyHoc::HocArray) {
    PyErr_SetString(PyExc_TypeError, "unsubscriptable object");
    return -1;
  }
  Arrayinfo* a = hocobj_aray(po->sym_, po->ho_);
  if (a->nsub - 1 != po->nindex_) {
    PyErr_SetString(PyExc_TypeError, "wrong number of subscripts");
    return -1;
  }
  if (araychk(a, po, i)) {
    return -1;
  }
  if (po->ho_) {
    if (po->sym_->type == SECTION) {
      PyErr_SetString(PyExc_TypeError, "not assignable");
    } else {
      eval_component(po, i);
      err = set_final_from_stk(arg);
    }
  } else {  // must be a top level intermediate
    HocTopContextSet switch (po->sym_->type) {
      case VAR:
        hocobj_pushtop(po, po->sym_, i);
        hoc_evalpointer();
        --po->nindex_;
        err = PyArg_Parse(arg, "d", hoc_pxpop()) != 1;
        break;
      case OBJECTVAR: {
        hocobj_pushtop(po, 0, i);
        hocobj_objectvar(po->sym_);
        --po->nindex_;
        Object** op;
        op = hoc_objpop();
        PyObject* pyo;
        if (PyArg_Parse(arg, "O", &pyo) == 1) {
          Object* ho = nrnpy_po2ho(pyo);
          hoc_obj_unref(*op);
          *op = ho;
          err = 0;
        } else {
          err = 1;
        }
        break;
      }
      default:
        PyErr_SetString(PyExc_TypeError, "not assignable");
        break;
    }
    HocContextRestore
  }
  return err;
}

static PyObject* mkref(PyObject* self, PyObject* args) {
  PyObject* pa;
  PyHocObject* result;
  if (PyArg_ParseTuple(args, "O", &pa) == 1) {
    result = (PyHocObject*)hocobj_new(hocobject_type, 0, 0);
    if (nrnpy_numbercheck(pa)) {
      result->type_ = PyHoc::HocRefNum;
      PyObject* pn = PyNumber_Float(pa);
      result->u.x_ = PyFloat_AsDouble(pn);
      Py_XDECREF(pn);
    } else if (is_python_string(pa)) {
      result->type_ = PyHoc::HocRefStr;
      result->u.s_ = 0;
      Py2NRNString str(pa);
      char* cpa = str.c_str();
      hoc_assign_str(&result->u.s_, cpa);
    } else {
      result->type_ = PyHoc::HocRefObj;
      result->u.ho_ = nrnpy_po2ho(pa);
    }
    return (PyObject*)result;
  }
  PyErr_SetString(PyExc_TypeError,
                  "single arg must be number, string, or Object");
  return NULL;
}

static PyObject* cpp2refstr(char** cpp) {
  // If cpp is from a hoc_temp_charptr (see src/oc/code.c) then create a
  // HocRefStr and copy *cpp. Otherwise, assume it is from a hoc strdef
  // or a HocRefStr which is persistent over the life time of this returned
  // PyObject so that it is safe to create a HocRefPStr such that
  // u.pstr_ = cpp and it is not needed
  // for the HocRefPStr destructor to delete either u.pstr_ or *u.pstr_.
  
  assert(cpp && *cpp); // not really sure about the *cpp
  PyHocObject* result = (PyHocObject*)hocobj_new(hocobject_type, 0, 0);
  if (hoc_is_temp_charptr(cpp)) { // return HocRefStr HocObject.
    result->type_ = PyHoc::HocRefStr;
    result->u.s_ = 0;
    hoc_assign_str(&result->u.s_, *cpp);    
  }else{
    result->type_ = PyHoc::HocRefPStr;
    result->u.pstr_ = cpp;
  }
  return (PyObject*)result;
}

static PyObject* setpointer(PyObject* self, PyObject* args) {
  PyObject* ref, *name, *pp, * result = NULL;
  if (PyArg_ParseTuple(args, "O!OO", hocobject_type, &ref, &name, &pp) == 1) {
    PyHocObject* href = (PyHocObject*)ref;
    double** ppd = 0;
    if (href->type_ != PyHoc::HocScalarPtr) {
      goto done;
    }
    if (PyObject_TypeCheck(pp, hocobject_type)) {
      PyHocObject* hpp = (PyHocObject*)pp;
      if (hpp->type_ != PyHoc::HocObject) {
        goto done;
      }
      Py2NRNString str(name);
      char* n = str.c_str();
      if (!n) {
        goto done;
      }
      Symbol* sym = getsym(n, hpp->ho_, 0);
      if (!sym || sym->type != RANGEVAR || sym->subtype != NRNPOINTER) {
        goto done;
      }
      Prop* prop = ob2pntproc_0(hpp->ho_)->prop;
      if (!prop) {
        PyErr_SetString(PyExc_TypeError, "Point_process not located in a section");
        return NULL;
      }
      ppd = &prop->dparam[sym->u.rng.index].pval;
    } else {
      ppd = nrnpy_setpointer_helper(name, pp);
      if (!ppd) {
        goto done;
      }
    }
    *ppd = href->u.px_;
    result = Py_None;
    Py_INCREF(result);
  }
done:
  if (!result) {
    PyErr_SetString(PyExc_TypeError,
                    "setpointer(_ref_hocvar, 'POINTER_name', point_process or "
                    "nrn.Mechanism))");
  }
  return result;
}

static PyObject* hocobj_vptr(PyObject* pself, PyObject* args) {
  Object* ho = ((PyHocObject*)pself)->ho_;
  PyObject* po = NULL;
  if (ho) {
    po = Py_BuildValue("O", PyLong_FromVoidPtr(ho));
  }
  if (!po) {
    PyErr_SetString(PyExc_TypeError, "HocObject does not wrap a Hoc Object");
  }
  return po;
}

static long hocobj_hash(PyHocObject* self) { return castptr2long self->ho_; }

PyObject* nrn_ptr_richcmp(void* self_ptr, void* other_ptr, int op) {
  bool result = false;
  switch (op) {
    case Py_LT:
      result = self_ptr < other_ptr;
      break;
    case Py_LE:
      result = self_ptr <= other_ptr;
      break;
    case Py_EQ:
      result = self_ptr == other_ptr;
      break;
    case Py_NE:
      result = self_ptr != other_ptr;
      break;
    case Py_GT:
      result = self_ptr > other_ptr;
      break;
    case Py_GE:
      result = self_ptr >= other_ptr;
      break;
  }
  if (result) {
    Py_RETURN_TRUE;
  }
  Py_RETURN_FALSE;
}

// TODO: unfortunately, this duplicates code from hocobj_same; consolidate?
static PyObject* hocobj_richcmp(PyHocObject* self, PyObject* other, int op) {
  void* self_ptr = (void*)(self->ho_);
  void* other_ptr = (void*)other;
  bool are_equal = true;
  if (PyObject_TypeCheck(other, hocobject_type)) {
    if (((PyHocObject*)other)->type_ == self->type_) {
      switch(self->type_) {
        case PyHoc::HocRefNum:
        case PyHoc::HocRefStr:
        case PyHoc::HocRefObj:
        case PyHoc::HocRefPStr:
          /* only same objects can point to same h.ref */
          self_ptr = (void*) self;
          break;
        case PyHoc::HocFunction:
          if (self->ho_ != (void*)(((PyHocObject*)other)->ho_)) {
            if (op == Py_NE) {
              Py_RETURN_TRUE;
            } else if (op == Py_EQ) {
              Py_RETURN_FALSE;
            }
            /* different classes, comparing < or > doesn't make sense */
            PyErr_SetString(PyExc_TypeError, "this comparison is undefined");
            return NULL;
          }
          self_ptr = (void*) self->sym_;
          other_ptr = (void*) (((PyHocObject*)other)->sym_);
          break;
        case PyHoc::HocScalarPtr:
          self_ptr = self->u.px_;
          other_ptr = (void*)(((PyHocObject*)other)->u.px_);
          break;
        case PyHoc::HocArrayIncomplete:
        case PyHoc::HocArray:
          if (op != Py_EQ && op != Py_NE) {
            /* comparing partial arrays doesn't make sense */
            PyErr_SetString(PyExc_TypeError, "this comparison is undefined");
            return NULL;
          }
          if (self->ho_ != (void*)(((PyHocObject*)other)->ho_)) {
            /* different objects */
            other_ptr = (void*)(((PyHocObject*)other)->ho_);
            break;
          }
          if (self->nindex_ != (((PyHocObject*)other)->nindex_) || self->sym_ != (((PyHocObject*)other)->sym_)) {
            if (op == Py_NE) {
              Py_RETURN_TRUE;
            }
            Py_RETURN_FALSE;
          }
          for (int i = 0; i < self->nindex_; i++) {
            if (self->indices_[i] != ((PyHocObject*)other)->indices_[i]) {
              are_equal = false;
            }
          }
          if (are_equal == (op == Py_EQ)) {
            Py_RETURN_TRUE;
          }
          Py_RETURN_FALSE;
        default:
          other_ptr = (void*)(((PyHocObject*)other)->ho_);
      }
    } else {
      if (op == Py_EQ) {
        Py_RETURN_FALSE;
      } else if (op == Py_NE) {
        Py_RETURN_TRUE;
      } 
      /* different NEURON object types are incomperable besides for (in)equality */
      PyErr_SetString(PyExc_TypeError, "this comparison is undefined");
      return NULL;
    }
  }
  return nrn_ptr_richcmp(self_ptr, other_ptr, op);
}

static PyObject* hocobj_same(PyHocObject* pself, PyObject* args) {
  PyObject* po;
  if (PyArg_ParseTuple(args, "O", &po)) {
    if (PyObject_TypeCheck(po, hocobject_type)) {
      if (((PyHocObject*)po)->ho_ == pself->ho_) {
        Py_RETURN_TRUE;
      }
    }
    Py_RETURN_FALSE;
  }
  return NULL;
}

static char* double_array_interface(PyObject* po, long& stride) {
  void* data = 0;
  PyObject* pstride;
  PyObject* psize;
  if (PyObject_HasAttrString(po, "__array_interface__")) {
    PyObject* ai = PyObject_GetAttrString(po, "__array_interface__");
    Py2NRNString typestr(PyDict_GetItemString(ai, "typestr"));
    if (strcmp(typestr.c_str(), array_interface_typestr) == 0) {
      data = PyLong_AsVoidPtr(
          PyTuple_GetItem(PyDict_GetItemString(ai, "data"), 0));
      // printf("double_array_interface idata = %ld\n", idata);
      if (PyErr_Occurred()) {
        data = 0;
      }
      pstride = PyDict_GetItemString(ai, "strides");
      if (pstride == Py_None) {
        stride = 8;
      } else if (PyTuple_Check(pstride)) {
        if (PyTuple_Size(pstride) == 1) {
          psize = PyTuple_GetItem(pstride, 0);
          if (PyLong_Check(psize)) {
            stride = PyLong_AsLong(psize);
          } else if (PyInt_Check(psize)) {
            stride = PyInt_AS_LONG(psize);

          } else {
            PyErr_SetString(PyExc_TypeError,
                            "array_interface stride element of invalid type.");
            data = 0;
          }

        } else
          data = 0;  // don't handle >1 dimensions
      } else {
        PyErr_SetString(PyExc_TypeError,
                        "array_interface stride object of invalid type.");
        data = 0;
      }
    }
    Py_DECREF(ai);
  }
  return (char*)data;
}

static IvocVect* nrnpy_vec_from_python(void* v) {
  Vect* hv = (Vect*)v;
  //	printf("%s.from_array\n", hoc_object_name(hv->obj_));
  Object* ho = *hoc_objgetarg(1);
  if (ho->ctemplate->sym != nrnpy_pyobj_sym_) {
    hoc_execerror(hoc_object_name(ho), " is not a PythonObject");
  }
  PyObject* po = nrnpy_hoc2pyobject(ho);
  Py_INCREF(po);
  if (!PySequence_Check(po)) {
    if (!PyIter_Check(po)) {
      hoc_execerror(
          hoc_object_name(ho),
          " does not support the Python Sequence or Iterator protocol");
    }
    PyObject* iterator = PyObject_GetIter(po);
    assert(iterator != NULL);
    int i = 0;
    PyObject* p;
    while ((p = PyIter_Next(iterator)) != NULL) {
      if (!PyNumber_Check(p)) {
        char buf[50];
        sprintf(buf, "item %d not a number", i);
        hoc_execerror(buf, 0);
      }
      hv->resize_chunk(i + 1);
      hv->elem(i++) = PyFloat_AsDouble(p);
      Py_DECREF(p);
    }
    Py_DECREF(iterator);
  } else {
    int size = PySequence_Size(po);
    //		printf("size = %d\n", size);
    hv->resize(size);
    double* x = vector_vec(hv);
    long stride;
    char* y = double_array_interface(po, stride);
    if (y) {
      for (int i = 0, j = 0; i < size; ++i, j += stride) {
        x[i] = *(double*)(y + j);
      }
    } else {
      for (int i = 0; i < size; ++i) {
        PyObject* p = PySequence_GetItem(po, i);
        if (!PyNumber_Check(p)) {
          char buf[50];
          sprintf(buf, "item %d not a number", i);
          hoc_execerror(buf, 0);
        }
        x[i] = PyFloat_AsDouble(p);
        Py_DECREF(p);
      }
    }
  }
  Py_DECREF(po);
  return hv;
}

static PyObject* (*vec_as_numpy)(int, double*);
int nrnpy_set_vec_as_numpy(PyObject* (*p)(int, double*)) {
  vec_as_numpy = p;
  return 0;
}

int nrnpy_set_toplevel_callbacks(PyObject* rvp_plot0, PyObject* plotshape_plot0, PyObject* get_mech_object_0) {
  rvp_plot = rvp_plot0;
  plotshape_plot = plotshape_plot0;
  get_mech_object_ = get_mech_object_0;
  return 0;
}

static PyObject* gui_callback=NULL;
int nrnpy_set_gui_callback(PyObject* new_gui_callback) {
  gui_callback = new_gui_callback;
  return 0;
}

static double object_to_double_(Object* obj) {
  PyObject* const pyobj = nrnpy_ho2po(obj);
  Py_INCREF(pyobj);
  const double result = PyFloat_AsDouble(pyobj);
  Py_DECREF(pyobj);
  return result;
}

static void* nrnpy_get_pyobj_(Object* obj) {
  // returns something wrapping a PyObject if it is a PyObject else NULL
  if (obj->ctemplate->sym == nrnpy_pyobj_sym_) {
    return (void*) nrnpy_ho2po(obj);
  }
  return NULL;
}

static void nrnpy_decref_(void* pyobj) {
  // note: this assumes that pyobj is really a PyObject
  if (pyobj) {
    Py_DECREF((PyObject*) pyobj);
  }
}

static PyObject* gui_helper_3_helper_(const char* name, Object* obj, int handle_strptr) {
  int narg = 1;
  while (ifarg(narg)) {
    narg++;
  }
  narg--;
  PyObject* args = PyTuple_New(narg + 3);
  PyObject* pyname = PyString_FromString(name);
  PyTuple_SetItem(args, 0, pyname);
  for(int iarg=0; iarg<narg; iarg++) {
    const int iiarg = iarg + 1;
    if (hoc_is_object_arg(iiarg)) {
      PyObject* active_obj = nrnpy_ho2po(*hoc_objgetarg(iiarg));
      PyTuple_SetItem(args, iarg + 3, active_obj);
    } else if (hoc_is_pdouble_arg(iiarg)) {
      PyHocObject* ptr_nrn = (PyHocObject*)hocobj_new(hocobject_type, 0, 0);
      ptr_nrn->type_ = PyHoc::HocScalarPtr;
      ptr_nrn->u.px_ = hoc_pgetarg(iiarg);
      PyObject* py_ptr = (PyObject*) ptr_nrn;
      Py_INCREF(py_ptr);
      PyTuple_SetItem(args, iarg + 3, py_ptr);
    } else if (hoc_is_str_arg(iiarg)) {
        if (handle_strptr > 0) {
          char** str_arg = hoc_pgargstr(iiarg);
          PyObject* py_ptr = cpp2refstr(str_arg);
          Py_INCREF(py_ptr);
          PyTuple_SetItem(args, iarg + 3, py_ptr);
        } else {
          PyObject* py_str = PyString_FromString(gargstr(iiarg));
          PyTuple_SetItem(args, iarg + 3, py_str); }
    } else if (hoc_is_double_arg(iiarg)) {
      PyObject* py_double = PyFloat_FromDouble(*getarg(iiarg));
      PyTuple_SetItem(args, iarg + 3, py_double);
    }
  }
  PyObject* my_obj;
  if (obj) {
    // there's a problem with this: if obj is intrinisically a PyObject, then this is increasing it's refcount and that's
    my_obj = nrnpy_ho2po(obj);
  } else {
    my_obj = Py_None;
    Py_INCREF(Py_None);
  }
  PyTuple_SetItem(args, 1, my_obj); // steals a reference
  PyObject* my_obj2;
  if (hoc_thisobject && name[0] != '~') {
    my_obj2 = nrnpy_ho2po(hoc_thisobject); // in the case of a HOC object, such as happens with List.browser, the ref count will be 1
  } else {
    my_obj2 = Py_None;
    Py_INCREF(Py_None);
  }

  PyTuple_SetItem(args, 2, my_obj2); // steals a reference to my_obj2
  PyObject* po = PyObject_CallObject(gui_callback, args);
  if (PyErr_Occurred()) {
    // if there was an error, display it and return 0. 
    // It's not a great solution, but it beats segfaulting
    PyErr_Print();
    po = PyLong_FromLong(0);
  }
  Py_DECREF(args); // Note: this decreases the ref count of my_obj and my_obj2
  return po;
}

static Object** gui_helper_3_(const char* name, Object* obj, int handle_strptr) {
  if (gui_callback) {
    PyObject* po = gui_helper_3_helper_(name, obj, handle_strptr);
    // TODO: something that allows None (currently nrnpy_po2ho returns NULL if po == Py_None)
    Object* ho = nrnpy_po2ho(po);
    Py_DECREF(po);
    if (ho) {
      --ho->refcount;
    }
    return hoc_temp_objptr(ho);
  }
  return NULL;
}

static char** gui_helper_3_str_(const char* name, Object* obj, int handle_strptr) {
  if (gui_callback) {
    PyObject* po = gui_helper_3_helper_(name, obj, handle_strptr);
    char** ts = hoc_temp_charptr();
    Py2NRNString str(po, true);
    *ts = str.c_str();
    // TODO: is there a memory leak here? do I need to: s2free.push_back(*ts);    
    Py_DECREF(po);
    return ts;
  }
  return NULL;
}


static Object** gui_helper_(const char* name, Object* obj) {
  return gui_helper_3_(name, obj, 0);
}

static Object** vec_as_numpy_helper(int size, double* data) {
  if (vec_as_numpy) {
    PyObject* po = (*vec_as_numpy)(size, data);
    if (po != Py_None) {
      Object* ho = nrnpy_po2ho(po);
      Py_DECREF(po);
      --ho->refcount;
      return hoc_temp_objptr(ho);
    }
  }
  hoc_execerror("Vector.as_numpy() error", 0);
  return NULL;
}

static Object** nrnpy_vec_to_python(void* v) {
  Vect* hv = (Vect*)v;
  int size = hv->capacity();
  double* x = vector_vec(hv);
  //	printf("%s.to_array\n", hoc_object_name(hv->obj_));
  PyObject* po;
  Object* ho = 0;

  // as_numpy_array=True is the case where this function is being called by the
  // ivocvect __array__ member
  // as such perhaps we should check here that no arguments were passed
  // although this should be the case unless the function is erroneously called
  // by the user.

  if (ifarg(1)) {
    ho = *hoc_objgetarg(1);
    if (ho->ctemplate->sym != nrnpy_pyobj_sym_) {
      hoc_execerror(hoc_object_name(ho), " is not a PythonObject");
    }
    po = nrnpy_hoc2pyobject(ho);
    if (!PySequence_Check(po)) {
      hoc_execerror(hoc_object_name(ho), " is not a Python Sequence");
    }
    if (size != PySequence_Size(po)) {
      hoc_execerror(hoc_object_name(ho),
                    "Python Sequence not same size as Vector");
    }
  } else {
    if ((po = PyList_New(size)) == NULL) {
      hoc_execerror("Could not create new Python List with correct size.", 0);
    }

    ho = nrnpy_po2ho(po);
    Py_DECREF(po);
    --ho->refcount;
  }
  //	printf("size = %d\n", size);
  long stride;
  char* y = double_array_interface(po, stride);
  if (y) {
    for (int i = 0, j = 0; i < size; ++i, j += stride) {
      *(double*)(y + j) = x[i];
    }
  } else if (PyList_Check(po)) {  // PySequence_SetItem does DECREF of old items
    for (int i = 0; i < size; ++i) {
      PyObject* pn = PyFloat_FromDouble(x[i]);
      if (!pn || PyList_SetItem(po, i, pn) == -1) {
        char buf[50];
        sprintf(buf, "%d of %d", i, size);
        hoc_execerror("Could not set a Python Sequence item", buf);
      }
    }
  } else {  // assume PySequence_SetItem works
    for (int i = 0; i < size; ++i) {
      PyObject* pn = PyFloat_FromDouble(x[i]);
      if (!pn || PySequence_SetItem(po, i, pn) == -1) {
        char buf[50];
        sprintf(buf, "%d of %d", i, size);
        hoc_execerror("Could not set a Python Sequence item", buf);
      }
      Py_DECREF(pn);
    }
  }
  return hoc_temp_objptr(ho);
}

static Object* rvp_rxd_to_callable_(Object* obj) {
  if (obj) {
    PyObject* py_obj = nrnpy_ho2po(obj);
    PyObject* result = PyObject_CallFunctionObjArgs(nrnpy_rvp_pyobj_callback, py_obj, NULL);
    Py_DECREF(py_obj);
    if (py_obj != result) {
      // for now, this only happens when using rangevarplot with rxd
      // prevents keeping section references that should not exist
      hoc_obj_unref(obj);
    }
    Object* obj_result = nrnpy_po2ho(result);
    Py_DECREF(result);  // the previous line incremented the reference count
    return obj_result;
  } else {
    return 0;
  }
}


PyObject* get_plotshape_data(PyObject* sp) {
  PyHocObject* pho = (PyHocObject*) sp;
  ShapePlotInterface* spi;
  if (!is_obj_type(pho->ho_, "PlotShape")) {
    PyErr_SetString(PyExc_TypeError, "get_plotshape_variable only takes PlotShape objects");
    return NULL;
  }
  void* that = pho->ho_->u.this_pointer;
#if HAVE_IV
IFGUI
  spi = ((ShapePlot*) that);
} else {
  spi = ((ShapePlotData*) that);
ENDGUI
#else
  spi = ((ShapePlotData*) that);
#endif
  Object* sl = spi->neuron_section_list();
  PyObject* py_sl = nrnpy_ho2po(sl);
  PyObject* py_obj = (PyObject*) spi->varobj();
  if (!py_obj) {
    py_obj = Py_None;
  }
  // NOte: O increases the reference count; N does not
  return Py_BuildValue("sOffN",spi->varname(), py_obj, spi->low(), spi->high(), py_sl);
}

// poorly follows __reduce__ and __setstate__
// from numpy/core/src/multiarray/methods.c
static PyObject* hocpickle_reduce(PyObject* self, PyObject* args) {
  // printf("hocpickle_reduce\n");
  PyHocObject* pho = (PyHocObject*)self;
  if (!is_obj_type(pho->ho_, "Vector")) {
    PyErr_SetString(PyExc_TypeError,
                    "HocObject: Only Vector instance can be pickled");
    return NULL;
  }
  Vect* vec = (Vect*)pho->ho_->u.this_pointer;

  // neuron module has a _pkl method that returns h.Vector(0)
  PyObject* mod = PyImport_ImportModule("neuron");
  if (mod == NULL) {
    return NULL;
  }
  PyObject* obj = PyObject_GetAttrString(mod, "_pkl");
  Py_DECREF(mod);
  if (obj == NULL) {
    PyErr_SetString(PyExc_Exception, "neuron module has no _pkl method.");
    return NULL;
  }

  PyObject* ret = PyTuple_New(3);
  if (ret == NULL) {
    return NULL;
  }
  PyTuple_SET_ITEM(ret, 0, obj);
  PyTuple_SET_ITEM(ret, 1, Py_BuildValue("(N)", PyInt_FromLong(0)));
  // see numpy implementation if more ret[1] stuff needed in case we
  // pickle anything but a hoc Vector. I don't think ret[1] can be None.

  // Fill object's state. Tuple with 4 args:
  // pickle version, 2.0 bytes to determine if swapbytes needed,
  // vector size, string data
  PyObject* state = PyTuple_New(4);
  if (state == NULL) {
    Py_DECREF(ret);
    return NULL;
  }
  PyTuple_SET_ITEM(state, 0, PyInt_FromLong(1));
  double x = 2.0;
  PyObject* str = PyBytes_FromStringAndSize((const char*)(&x), sizeof(double));
  if (str == NULL) {
    Py_DECREF(ret);
    Py_DECREF(state);
    return NULL;
  }
  PyTuple_SET_ITEM(state, 1, str);
  PyTuple_SET_ITEM(state, 2, PyInt_FromLong(vec->capacity()));
  str = PyBytes_FromStringAndSize((const char*)vector_vec(vec),
                                  vec->capacity() * sizeof(double));
  if (str == NULL) {
    Py_DECREF(ret);
    Py_DECREF(state);
    return NULL;
  }
  PyTuple_SET_ITEM(state, 3, str);
  PyTuple_SET_ITEM(ret, 2, state);
  return ret;
}

// following copied (except for nrn_need_byteswap line) from NEURON ivocvect.cpp
#define BYTEHEADER \
  uint32_t _II__;  \
  char* _IN__;     \
  char _OUT__[16]; \
  int BYTESWAP_FLAG = 0;
#define BYTESWAP(_X__, _TYPE__)                           \
  if (BYTESWAP_FLAG == 1) {                               \
    _IN__ = (char*)&(_X__);                               \
    for (_II__ = 0; _II__ < sizeof(_TYPE__); _II__++) {   \
      _OUT__[_II__] = _IN__[sizeof(_TYPE__) - _II__ - 1]; \
    }                                                     \
    (_X__) = *((_TYPE__*)&_OUT__);                        \
  }

static PyObject* hocpickle_setstate(PyObject* self, PyObject* args) {
  BYTEHEADER
  int version = -1;
  int size = -1;
  PyObject* rawdata = NULL;
  PyObject* endian_data;
  PyHocObject* pho = (PyHocObject*)self;
  // printf("hocpickle_setstate %s\n", hoc_object_name(pho->ho_));
  Vect* vec = (Vect*)pho->ho_->u.this_pointer;
  if (!PyArg_ParseTuple(args, "(iOiO)", &version, &endian_data, &size,
                        &rawdata)) {
    return NULL;
  }
  Py_INCREF(endian_data);
  Py_INCREF(rawdata);
  // printf("hocpickle version=%d size=%d\n", version, size);
  vector_resize(vec, size);
  if (!PyBytes_Check(rawdata) || !PyBytes_Check(endian_data)) {
    PyErr_SetString(PyExc_TypeError, "pickle not returning string");
    Py_DECREF(endian_data);
    Py_DECREF(rawdata);
    return NULL;
  }
  char* datastr;
  Py_ssize_t len;
  if (PyBytes_AsStringAndSize(endian_data, &datastr, &len) < 0) {
    Py_DECREF(endian_data);
    Py_DECREF(rawdata);
    return NULL;
  }
  if (len != sizeof(double)) {
    PyErr_SetString(PyExc_ValueError, "endian_data size is not sizeof(double)");
    Py_DECREF(endian_data);
    Py_DECREF(rawdata);
    return NULL;
  }
  BYTESWAP_FLAG = 0;
  if (*((double*)datastr) != 2.0) {
    BYTESWAP_FLAG = 1;
  }
  Py_DECREF(endian_data);
  // printf("byteswap = %d\n", BYTESWAP_FLAG);
  if (PyBytes_AsStringAndSize(rawdata, &datastr, &len) < 0) {
    Py_DECREF(rawdata);
    return NULL;
  }
  if (len != size * sizeof(double)) {
    PyErr_SetString(PyExc_ValueError, "buffer size does not match array size");
    Py_DECREF(rawdata);
    return NULL;
  }
  if (BYTESWAP_FLAG) {
    double* x = (double*)datastr;
    for (int i = 0; i < size; ++i) {
      BYTESWAP(x[i], double)
    }
  }
  memcpy((char*)vector_vec(vec), datastr, len);
  Py_DECREF(rawdata);
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject* libpython_path(PyObject* self, PyObject* args) {
#if defined(HAVE_DLFCN_H) && !defined(MINGW)
  Dl_info info;
  int rval = dladdr((const void*)Py_Initialize, &info);
  if (!rval) {
    PyErr_SetString(PyExc_Exception, "dladdr: Py_Initialize could not be matched to a shared object");
    return NULL;
  }
  if (!info.dli_fname) {
    PyErr_SetString(PyExc_Exception, "dladdr: No symbol matching Py_Initialize could be found.");
    return NULL;
  }
  return Py_BuildValue("s", info.dli_fname);
#else
  Py_INCREF(Py_None);
  return Py_None;
#endif
}

// available for every HocObject
static PyMethodDef hocobj_methods[] = {
    {"baseattr", hocobj_baseattr, METH_VARARGS,
     "To allow use of an overrided base method"},
    {"hocobjptr", hocobj_vptr, METH_NOARGS, "Hoc Object pointer as a long int"},
    {"same", (PyCFunction)hocobj_same, METH_VARARGS,
     "o1.same(o2) return True if o1 and o2 wrap the same internal HOC Object"},
    {"hname", hocobj_name, METH_NOARGS,
     "More specific than __str__() or __attr__()."},
    {"__reduce__", hocpickle_reduce, METH_VARARGS, "pickle interface"},
    {"__setstate__", hocpickle_setstate, METH_VARARGS, "pickle interface"},
    {NULL, NULL, 0, NULL}};

// only for a HocTopLevelInterpreter type HocObject
static PyMethodDef toplevel_methods[] = {
    {"ref", mkref, METH_VARARGS,
     "Wrap to allow call by reference in a hoc function"},
    {"cas", nrnpy_cas, METH_VARARGS, "Return the currently accessed section."},
    {"allsec", nrnpy_forall, METH_VARARGS,
     "Return iterator over all sections."},
    {"Section", (PyCFunction)nrnpy_newsecobj, METH_VARARGS | METH_KEYWORDS,
     "Return a new Section"},
    {"setpointer", setpointer, METH_VARARGS,
     "Assign hoc variable address to NMODL POINTER"},
    {"libpython_path", libpython_path, METH_NOARGS,
     "Return full path to file that contains Py_Initialize()"},
    {NULL, NULL, 0, NULL}};

static void add2topdict(PyObject* dict) {
  for (PyMethodDef* meth = toplevel_methods; meth->ml_name != NULL; meth++) {
    int err;
    PyObject* nn = Py_BuildValue("s", meth->ml_doc);
    if (!nn) {
      return;
    }
    err = PyDict_SetItemString(dict, meth->ml_name, nn);
    Py_DECREF(nn);
    if (err) {
      return;
    }
  }
}

static PyObject* nrnpy_vec_math = NULL;

int nrnpy_vec_math_register(PyObject* callback) {
  nrnpy_vec_math = callback;
  return 0;
}

int nrnpy_rvp_pyobj_callback_register(PyObject* callback) {
  nrnpy_rvp_pyobj_callback = callback;
  return 0;
}

static bool pyobj_is_vector(PyObject* obj) {
  if (PyObject_TypeCheck(obj, hocobject_type)) {
    PyHocObject* obj_h = (PyHocObject*) obj;
    if (obj_h->type_ == PyHoc::HocObject) {
      // this is an object (e.g. Vector) not a function
      if (obj_h->ho_->ctemplate == hoc_vec_template_) {
        return true;
      }
    }
  }
  return false;
}

static PyObject* py_hocobj_math(const char* op, PyObject* obj1, PyObject* obj2) {
  bool potentially_valid = false;
  int reversed = 0;
  if (pyobj_is_vector(obj1)) {
    potentially_valid = true;
  } else if (pyobj_is_vector(obj2)) {
    potentially_valid = true;
    reversed = 1;
  }
  if (!potentially_valid){
    Py_INCREF(Py_NotImplemented);
    return Py_NotImplemented;
  }
  char buf[8];
  return PyObject_CallFunction(nrnpy_vec_math, strcpy(buf, "siOO"), op, reversed, obj1, obj2);
}

static PyObject* py_hocobj_math_unary(const char* op, PyObject* obj) {
  if (pyobj_is_vector(obj)) {
    char buf[8];
    return PyObject_CallFunction(nrnpy_vec_math, strcpy(buf, "siO"), op, 2, obj);
  }
  Py_INCREF(Py_NotImplemented);
  return Py_NotImplemented;
}

static PyObject* py_hocobj_add(PyObject* obj1, PyObject* obj2) {
  return py_hocobj_math("add", obj1, obj2);
}

static PyObject* py_hocobj_uabs(PyObject* obj) {
  return py_hocobj_math_unary("uabs", obj);
}

static PyObject* py_hocobj_uneg(PyObject* obj) {
  return py_hocobj_math_unary("uneg", obj);
}

static PyObject* py_hocobj_upos(PyObject* obj) {
  return py_hocobj_math_unary("upos", obj);
}

static PyObject* py_hocobj_sub(PyObject* obj1, PyObject* obj2) {
  return py_hocobj_math("sub", obj1, obj2);
}

static PyObject* py_hocobj_mul(PyObject* obj1, PyObject* obj2) {
  return py_hocobj_math("mul", obj1, obj2);
}

static PyObject* py_hocobj_div(PyObject* obj1, PyObject* obj2) {
  return py_hocobj_math("div", obj1, obj2);
}
static PyMemberDef hocobj_members[] = {{NULL, 0, 0, 0, NULL}};

#if (PY_MAJOR_VERSION >= 3)
#include "nrnpy_hoc_3.h"
#else
#include "nrnpy_hoc_2.h"
#endif

// Figure out the endian-ness of the system, and return
// 0 (error), '<' (little endian) or '>' (big endian)
char get_endian_character() {
  char endian_character = 0;

  PyObject* psys = PyImport_ImportModule("sys");
  if (psys == NULL) {
    PyErr_SetString(PyExc_ImportError,
                    "Failed to import sys to determine system byteorder.");
    return 0;
  }

  PyObject* pbo = PyObject_GetAttrString(psys, "byteorder");
  if (pbo == NULL) {
    PyErr_SetString(PyExc_AttributeError,
                    "sys module does not have attribute 'byteorder'!");
    return 0;
  }

  Py2NRNString byteorder(pbo);
  if (byteorder.c_str() == NULL) {
    return 0;
  }

  if (strcmp(byteorder.c_str(), "little") == 0) {
    endian_character = '<';
  } else if (strcmp(byteorder.c_str(), "big") == 0) {
    endian_character = '>';
  } else {
    PyErr_SetString(PyExc_RuntimeError, "Unknown system native byteorder.");
    return 0;
  }
  return endian_character;
}

static void sectionlist_helper_(void* sl, Object* args) {
  if (!args || args->ctemplate->sym != nrnpy_pyobj_sym_) {
    hoc_execerror("argument must be a Python iterable", "");
  }
  PyObject* pargs = nrnpy_hoc2pyobject(args);
  
  PyObject *iterator = PyObject_GetIter(pargs);
  PyObject *item;

  if (iterator == NULL) {
    PyErr_Clear();
    hoc_execerror("argument must be an iterable", "");
  }

  while ((item = PyIter_Next(iterator))) {
    if (!PyObject_TypeCheck(item, psection_type)) {
      hoc_execerror("iterable must contain only Section objects", 0);
    }
    NPySecObj* pysec = (NPySecObj*)item;
    lvappendsec_and_ref(sl, pysec->sec_);
    Py_DECREF(item);
  }

  Py_DECREF(iterator);
  if (PyErr_Occurred()) {
    PyErr_Clear();
    hoc_execerror("argument must be a Python iterable", "");
  }
}

/** value of neuron.coreneuron.enable as 0, 1 (-1 if error)
 *  TODO: seems like this could be generalized so that
 *  additional cases would require less code.
*/
extern int (*nrnpy_nrncore_enable_value_p_)();
static int nrncore_enable_value() {
  PyObject* modules = PyImport_GetModuleDict();
  if (modules) {
    PyObject* module = PyDict_GetItemString(modules, "neuron.coreneuron");
    if (module) {
      PyObject* val = PyObject_GetAttrString(module, "enable");
      if (val) {
        long enable = PyLong_AsLong(val);
        Py_DECREF(val);
        if (enable != -1) {
          return enable;
        }
      }
    }
  }
  if (PyErr_Occurred()) {
    PyErr_Print();
    return -1;
  }
  return 0;
}

/** Gets the python string returned by  neuron.coreneuron.nrncore_arg(tstop)
    return a strdup() copy of the string which should be free when the caller
    finishes with it. Return NULL if error or bool(neuron.coreneuron.enable)
    is False.
*/
extern char* (*nrnpy_nrncore_arg_p_)(double tstop);
static char* nrncore_arg(double tstop) {
  PyObject* modules = PyImport_GetModuleDict();
  if (modules) {
    PyObject* module = PyDict_GetItemString(modules, "neuron.coreneuron");
    if (module) {
      PyObject* callable = PyObject_GetAttrString(module, "nrncore_arg");
      if (callable) {
        PyObject* ts = Py_BuildValue("(d)", tstop);
        if (ts) {
          PyObject* arg = PyObject_CallObject(callable, ts);
          Py_DECREF(ts);
          if (arg) {
            Py2NRNString str(arg);
            Py_DECREF(arg);
            if (strlen(str.c_str()) > 0) {
              return strdup(str.c_str());
            }
          }
        }
      }
    }
  }
  if (PyErr_Occurred()) {
    PyErr_Print();
  }
  return NULL;
}

myPyMODINIT_FUNC nrnpy_hoc() {
  PyObject* m;
  nrnpy_vec_from_python_p_ = nrnpy_vec_from_python;
  nrnpy_vec_to_python_p_ = nrnpy_vec_to_python;
  nrnpy_vec_as_numpy_helper_ = vec_as_numpy_helper;
  nrnpy_sectionlist_helper_ = sectionlist_helper_;
  nrnpy_gui_helper_ = gui_helper_;
  nrnpy_gui_helper3_ = gui_helper_3_;
  nrnpy_gui_helper3_str_ = gui_helper_3_str_;
  nrnpy_get_pyobj = nrnpy_get_pyobj_;
  nrnpy_decref = nrnpy_decref_;
  nrnpy_nrncore_arg_p_ = nrncore_arg;
  nrnpy_nrncore_enable_value_p_ = nrncore_enable_value;
  nrnpy_object_to_double_ = object_to_double_;
  nrnpy_rvp_rxd_to_callable = rvp_rxd_to_callable_;
  PyLockGIL lock;

  char endian_character = 0;

#if PY_MAJOR_VERSION >= 3
  int err = 0;
  PyObject* modules = PyImport_GetModuleDict();
#if defined(__MINGW32__)
  if ((m = PyDict_GetItemString(modules, HOCMOD)) != NULL && PyModule_Check(m)) {
#else
  if ((m = PyDict_GetItemString(modules, "hoc")) != NULL && PyModule_Check(m)) {
#endif // __MINGW32__
    return m;
  }
  m = PyModule_Create(&hocmodule);
#else // PY_MAJOR_VERSION
#if defined(__MINGW32__)
  m = Py_InitModule3(HOCMOD, HocMethods, "HOC interaction with Python");
#else
  m = Py_InitModule3("hoc", HocMethods, "HOC interaction with Python");
#endif // __MINGW32__
#endif // PY_MAJOR_VERSION
  assert(m);
  Symbol* s = NULL;
#if PY_MAJOR_VERSION >= 3
  hocobject_type = (PyTypeObject*)PyType_FromSpec(&nrnpy_HocObjectType_spec);
#else
  hocobject_type = &nrnpy_HocObjectType;
#endif
  if (PyType_Ready(hocobject_type) < 0) goto fail;
  Py_INCREF(hocobject_type);
  // printf("AddObject HocObject\n");
  PyModule_AddObject(m, "HocObject", (PyObject*)hocobject_type);

  topmethdict = PyDict_New();
  for (PyMethodDef* meth = toplevel_methods; meth->ml_name != NULL; meth++) {
    PyObject* descr;
    int err;
    descr = PyDescr_NewMethod(hocobject_type, meth);
    assert(descr);
    err = PyDict_SetItemString(topmethdict, meth->ml_name, descr);
    Py_DECREF(descr);
    if (err < 0) {
      goto fail;
    }      
  }

  s = hoc_lookup("Vector");
  assert(s);
  hoc_vec_template_ = s->u.ctemplate;
  sym_vec_x = hoc_table_lookup("x", s->u.ctemplate->symtable);
  assert(sym_vec_x);
  s = hoc_lookup("List");
  assert(s);
  hoc_list_template_ = s->u.ctemplate;
  s = hoc_lookup("SectionList");
  assert(s);
  hoc_sectionlist_template_ = s->u.ctemplate;
  s = hoc_lookup("Matrix");
  assert(s);
  sym_mat_x = hoc_table_lookup("x", s->u.ctemplate->symtable);
  assert(sym_mat_x);
  s = hoc_lookup("NetCon");
  assert(s);
  sym_netcon_weight = hoc_table_lookup("weight", s->u.ctemplate->symtable);
  assert(sym_netcon_weight);

  nrnpy_nrn();
  endian_character = get_endian_character();
  if (endian_character == 0) goto fail;
  array_interface_typestr[0] = endian_character;

  // Setup bytesize in typestr
  snprintf(array_interface_typestr + 2, 3, "%ld", sizeof(double));
#if PY_MAJOR_VERSION >= 3
  err = PyDict_SetItemString(modules, "hoc", m);
  assert(err == 0);
//  Py_DECREF(m);
  return m;
fail:
  return NULL;
#else
fail:
  return;
#endif
}
}  // end of extern c
